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:
+29
@@ -0,0 +1,29 @@
|
||||
# 데이터 파일 (매 실행마다 재생성)
|
||||
GatherTradingData.xlsx
|
||||
GatherTradingData.json
|
||||
|
||||
# LibreOffice 임시 잠금 파일
|
||||
.~lock.*
|
||||
|
||||
# 빌드 산출물
|
||||
Temp/
|
||||
dist/
|
||||
|
||||
# OS / 에디터
|
||||
...
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.venv/
|
||||
*.egg-info/
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Claude 세션 캐시 (자동메모리 제외)
|
||||
.claude/projects/
|
||||
@@ -0,0 +1,95 @@
|
||||
# 은퇴자산포트폴리오 투자 에이전트 운영 지침
|
||||
|
||||
## 0. 최우선 원칙
|
||||
- 이 파일은 운영 인덱스다. 상세 규칙은 `governance/rules/*.yaml`와 `spec/*.yaml`를 우선한다.
|
||||
- 가격, 수량, TP/SL, 점수는 오직 `spec/13_formula_registry.yaml`와 하네스 산출값만 사용한다.
|
||||
- 임의 계산, 임의 가격, 임의 수량, 미등록 공식은 금지한다.
|
||||
- 하네스 결측은 `DATA_MISSING — 하네스 업데이트 필요`로만 표시한다.
|
||||
- 차단된 종목의 산출값은 숨기지 말고 shadow ledger로 투명하게 남긴다.
|
||||
|
||||
## 1. 읽는 순서
|
||||
1. `runtime/active_artifact_manifest.yaml`
|
||||
2. `Temp/final_decision_packet_active.json` (manifest alias)
|
||||
|
||||
## 1b. Critical Authority Files
|
||||
- `spec/00_execution_contract.yaml`
|
||||
- `spec/risk/aggregate_risk.yaml`
|
||||
- `spec/risk/portfolio_exposure.yaml`
|
||||
- `spec/14_raw_workbook_mapping.yaml`
|
||||
- `spec/15_account_snapshot_contract.yaml`
|
||||
- `spec/02_data_contract.yaml`
|
||||
- `spec/09_decision_flow.yaml`
|
||||
- `spec/12_field_dictionary.yaml`
|
||||
- `spec/13_formula_registry.yaml`
|
||||
|
||||
## 2. 문서 역할
|
||||
- `AGENTS.md`: 운영 헌법과 링크 인덱스.
|
||||
- `governance/agents_index.yaml`: rule file 목록과 hash migration index.
|
||||
- `governance/rules/*.yaml`: 장문의 세부 규칙.
|
||||
- `spec/*.yaml`: 계약, 공식, 게이트, 출력 계약의 원본 권위.
|
||||
- `src/quant_engine`: canonical Python package. schema/model parity와 reporting helper를 담는다.
|
||||
- `tools/*.py`: 검증/생성 CLI. 가능한 한 얇게 유지한다.
|
||||
- `Temp/*.json`: 런타임 산출물. 읽기 전용 취급이며 직접 편집하지 않는다.
|
||||
- `schemas/*.schema.json`: shape validation.
|
||||
|
||||
## 2b. Directory Routing / Serving
|
||||
- `spec/`: source of truth. 공식, 계약, 게이트, 출력 스키마의 최우선 읽기 경로.
|
||||
- `governance/`: 운영 규칙, 인덱스, 해시 마이그레이션, ADR, 템플릿.
|
||||
- `src/`: Python canonical implementation. 새 로직은 여기부터 반영한다.
|
||||
- `tools/`: build, validate, convert, audit CLI. 상태는 유지하되 핵심 로직은 두지 않는다.
|
||||
- `gas_event_calendar.gs`: 이벤트 캘린더 배포 호환 스텁. `seedEventCalendar_()` / `runEventRisk()` 진입점을 유지한다.
|
||||
- `Temp/`: 실행 결과와 캐시. 라우팅 대상은 아니며 runtime consumer만 읽는다.
|
||||
- `dist/`, `artifacts/`, `docs/`, `examples/`, `prompts/`, `schemas/`, `tests/`: 패키징/문서/검증/산출물 보조 경로.
|
||||
- `run_all`: 외부 스케줄러가 호출하는 진입점으로 유지한다. 실행 시 `run_all_invocation_mode=external_scheduler`를 기준으로 해석한다.
|
||||
|
||||
## 3. 하드 룰
|
||||
- 가격 임의 창작 금지.
|
||||
- 다중 조건 접속사 기반 주문문 금지 (모든 매도는 단일 sell priority table 및 waterfall 방식으로 선형 처리).
|
||||
- tick normalization 의무.
|
||||
- TP stale 값은 제거.
|
||||
- sell candidate가 2개 이상이면 sell priority table을 먼저 출력.
|
||||
- LLM은 하네스 판정을 번복하지 않으며, 임의로 사칙연산, 평균, 순위(rank) 등을 재계산하지 않고 context key를 그대로 copy-only하여 렌더링한다.
|
||||
- 외부 시장 데이터는 참고용이며 JSON harness가 우선한다.
|
||||
- provenance 없는 숫자는 보고서에 쓰지 않는다.
|
||||
- 추격 매수 방지를 위해 모든 매수 진입은 anti-late entry gate 검증을 필수로 통과한다.
|
||||
- 데이터 흐름은 단방향(Data -> Feature -> Decision -> Execution -> Report) 아키텍처 경계를 유지하며, renderer가 core 계산을 호출하는 역참조를 금지한다.
|
||||
- D+2 영업일 기준 현금을 즉시방어 자산으로 간주하고, 목표 예산 5억 원을 기준으로 포지션 사이징 및 리스크 버킷을 제어한다.
|
||||
- 매주 주말 리밸런싱(rebalance_required=true) 및 매월 1일/11일/21일 중간점검(mid_check_required=true) 운영 cadence를 준수한다.
|
||||
|
||||
## 4. 보고 규칙
|
||||
- 모든 숫자에는 반드시 provenance(출처)를 남기며, 출처가 유효하지 않거나 없는 숫자는 보고서 표기를 전면 배제(DATA_MISSING 처리)한다.
|
||||
- 보고서 첫 부분에는 portfolio health와 주요 차단 사유를 먼저 쓴다.
|
||||
- blocked/limited 상태라도 산출된 기준가, 손절가, 익절가, 수량은 숨기지 않는다.
|
||||
- narrative는 숫자와 게이트를 완화하는 표현을 쓰지 않는다.
|
||||
|
||||
## 5. 개발 규칙
|
||||
- 새 기능은 contract, schema, golden case, owner ledger를 먼저 만든다.
|
||||
- 구현은 Python canonical first, GAS adapter second다.
|
||||
- `tools/*.py`는 CLI wrapper에 가깝게 유지한다.
|
||||
- `gas_*.gs`는 thin adapter 방향으로 유지한다.
|
||||
- `src/quant_engine`는 canonical package로 유지한다.
|
||||
- `schemas/generated`와 `src/quant_engine/models/generated`는 schema/model parity를 유지한다.
|
||||
- 경로가 새로 생기면 `AGENTS.md`의 Directory Routing / Serving 섹션과 zip 화이트리스트를 함께 갱신한다.
|
||||
|
||||
## 6. 검증 규칙
|
||||
- `python tools/validate_specs.py`
|
||||
- `python tools/validate_golden_coverage_100.py`
|
||||
- `python tools/validate_calibration_registry_v1.py`
|
||||
- `python tools/validate_schema_model_generation_v1.py`
|
||||
- `python tools/validate_gas_thin_adapter_v1.py`
|
||||
- `python tools/validate_agents_shrink_v1.py`
|
||||
|
||||
## 7. Rule Index
|
||||
- [governance/agents_index.yaml](/C:/Temp/data_feed/governance/agents_index.yaml)
|
||||
- [governance/rules/00_core_locks.yaml](/C:/Temp/data_feed/governance/rules/00_core_locks.yaml)
|
||||
- [governance/rules/01_harness_contract.yaml](/C:/Temp/data_feed/governance/rules/01_harness_contract.yaml)
|
||||
- [governance/rules/02_portfolio_policy.yaml](/C:/Temp/data_feed/governance/rules/02_portfolio_policy.yaml)
|
||||
- [governance/rules/03_order_grammar.yaml](/C:/Temp/data_feed/governance/rules/03_order_grammar.yaml)
|
||||
- [governance/rules/04_reporting_contract.yaml](/C:/Temp/data_feed/governance/rules/04_reporting_contract.yaml)
|
||||
- [governance/rules/05_migration_hashes.yaml](/C:/Temp/data_feed/governance/rules/05_migration_hashes.yaml)
|
||||
|
||||
- 모든 수치 provenance 및 아키텍처 경계는 `spec/49_refactor_methodology_contract.yaml` 및 `ADR-0011`을 준수한다.
|
||||
- 가격/수량/공식을 LLM이 즉석 정의하지 않는다.
|
||||
- replay 표본을 live 운영성과로 혼입하지 않는다.
|
||||
- validation 실패를 무시하지 않는다.
|
||||
- deprecated artifact를 runtime source로 읽지 않는다.
|
||||
@@ -0,0 +1,136 @@
|
||||
# Core/Satellite Collector v4
|
||||
|
||||
은퇴자산용 코어/위성 후보 데이터 수집기입니다.
|
||||
|
||||
## v4 기본 정책
|
||||
|
||||
- 파라미터 없이 실행
|
||||
- 1차 유니버스: KOSPI 160개 + KOSDAQ 40개
|
||||
- 최종 후보: 100개
|
||||
- 최종 후보 내 KOSDAQ: 최대 20개
|
||||
- 1차 탐색 총량은 v3와 동일한 200개로 유지하여 호출 수 증가를 막습니다.
|
||||
|
||||
## 설치
|
||||
|
||||
```powershell
|
||||
npm install
|
||||
node core_satellite_collector.js
|
||||
```
|
||||
|
||||
OpenDART 공시까지 확인하려면:
|
||||
|
||||
```powershell
|
||||
$env:DART_API_KEY="발급받은키"
|
||||
node core_satellite_collector.js
|
||||
```
|
||||
|
||||
## 운영 표준
|
||||
|
||||
하네스/보고서/동기화까지 포함한 최종 게이트는 아래 순서를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run full-gate
|
||||
```
|
||||
|
||||
이 스크립트는 아래를 직렬로 수행합니다.
|
||||
|
||||
- `convert-data-json`
|
||||
- `validate-gas-call-arity`
|
||||
- `validate-proposal-reference`
|
||||
- `validate-harness-context`
|
||||
- `validate-operational-report-contract`
|
||||
- `audit-coverage`
|
||||
- `validate-harness-coverage-auditor`
|
||||
- `validate-strategy-tests-contract`
|
||||
- `validate-breakout-gate`
|
||||
- `validate-anti-whipsaw`
|
||||
- `validate-cash-raise-route`
|
||||
- `validate-brt-harness`
|
||||
- `validate-determinism`
|
||||
- `validate-alpha-execution-harness:strict`
|
||||
- `render-report-json`
|
||||
- `validate-report-json`
|
||||
- `validate-report-quality`
|
||||
- `validate-report-sync`
|
||||
|
||||
spec와 데이터 샘플 검증까지 포함한 전체 엄격 검증은 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run validate-engine-strict
|
||||
```
|
||||
|
||||
proposal 평가 이력까지 갱신하는 일일 실행은 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run daily-feedback-report
|
||||
```
|
||||
|
||||
백필 누적 원장(`backdata_feature_bank`) 상태를 즉시 검증하려면 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run validate-backdata-migration-state
|
||||
```
|
||||
|
||||
Outcome/Evaluation 복구 파이프라인(YOLO)을 한 번에 실행하려면 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run yolo-outcome-recovery
|
||||
```
|
||||
|
||||
GAS 함수 정의/호출 인자 수 불일치만 단독 점검하려면 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run validate-gas-call-arity
|
||||
```
|
||||
|
||||
사용자 판단용 제안표 하네스 출력이 실제로 존재하는지 점검하려면 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run validate-proposal-reference
|
||||
```
|
||||
|
||||
GAS `runHarnessRefresh_()` 반영 후에는 아래 강제 검증으로 올릴 수 있습니다.
|
||||
|
||||
```powershell
|
||||
npm run validate-proposal-reference:strict
|
||||
```
|
||||
|
||||
GAS 반영 후 `proposal_reference_json`까지 포함한 최종 엄격 게이트는 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run full-gate:proposal-strict
|
||||
```
|
||||
|
||||
spec/데이터 샘플까지 포함한 전체 엄격 검증은 아래를 사용합니다.
|
||||
|
||||
```powershell
|
||||
npm run validate-engine-proposal-strict
|
||||
```
|
||||
|
||||
## GAS 반영 체크리스트
|
||||
|
||||
`proposal_reference_json`을 실제 하네스 출력으로 승격하려면 아래 순서를 따릅니다.
|
||||
|
||||
1. Apps Script에 최신 [gas_harness_rows.gs](/C:/Temp/data_feed/gas_harness_rows.gs:73) 반영
|
||||
2. Apps Script에서 `runHarnessRefresh_()` 실행
|
||||
3. Google Sheets `harness_context` 시트에 아래 키 생성 확인
|
||||
- `proposal_reference_json`
|
||||
- `proposal_reference_lock`
|
||||
4. 로컬에서 `npm run convert-data-json` 실행
|
||||
5. `npm run validate-proposal-reference:strict` 실행
|
||||
6. `npm run full-gate:proposal-strict` 실행
|
||||
7. 최종 운영 전환 시 `npm run validate-engine-proposal-strict` 기준으로 사용
|
||||
|
||||
## 운영 리포트 계약
|
||||
|
||||
운영 리포트는 사람이 읽는 `Temp/operational_report.md`와 기계 검증용 `Temp/operational_report.json`을 함께 생성합니다.
|
||||
|
||||
- `operational_report.json`이 canonical 계약입니다.
|
||||
- `operational_report.md`는 표시용 렌더입니다.
|
||||
- JSON 스키마는 `schemas/operational_report.schema.json`을 사용합니다.
|
||||
- 계약 드리프트 검사는 `npm run validate-operational-report-contract`로 수행합니다.
|
||||
- 전체 게이트에는 `render-report-json -> validate-report-json -> validate-report-quality -> validate-report-sync` 순서가 포함됩니다.
|
||||
|
||||
전환 기준:
|
||||
- `validate-proposal-reference`가 `SKIP`이면 아직 GAS 산출물 미반영 상태
|
||||
- `validate-proposal-reference:strict`가 `PASS`여야 proposal 하네스 strict 전환 완료
|
||||
@@ -0,0 +1,507 @@
|
||||
meta:
|
||||
title: "은퇴자산 투자 포트폴리오 운용 지침 — Modular Manifest"
|
||||
version: "2026-05-20-HARNESS_V4"
|
||||
language: "ko-KR"
|
||||
timezone: "Asia/Seoul"
|
||||
currency: "KRW"
|
||||
target_asset_krw: 500000000
|
||||
target_deadline: "2026-12-31"
|
||||
purpose: >
|
||||
이 파일은 LLM이 투자 판단에 사용할 구조화 명세의 manifest다.
|
||||
상세 알고리즘은 spec/*.yaml에 분리하고, 보고서 양식은 RetirementAssetPortfolioReportTemplate.yaml에 둔다.
|
||||
자연어 설명보다 구조화 명세와 수치 공식이 우선한다.
|
||||
제공 raw 분석 데이터의 1차 LLM 입력은 'GatherTradingData.json'이며,
|
||||
xlsx는 GAS/Google Sheets 원본·감사·JSON 재생성 소스다.
|
||||
JSON의 필수 경로와 컬럼은 spec/02_data_contract.yaml 및 tools/validate_data_sample_json.py로 검증한다.
|
||||
|
||||
primary_raw_data:
|
||||
json: "GatherTradingData.json"
|
||||
source_workbook: "GatherTradingData.xlsx"
|
||||
role: "provided_raw_analysis_data_json"
|
||||
required_json_paths: ["data.data_feed", "data.sector_flow", "data.macro", "data.event_risk", "data.core_satellite"]
|
||||
validation_tool: "tools/validate_data_sample_json.py"
|
||||
conversion_tool: "tools/convert_xlsx_to_json.py"
|
||||
mapping_file: "spec/14_raw_workbook_mapping.yaml"
|
||||
account_snapshot_contract: "spec/15_account_snapshot_contract.yaml"
|
||||
precedence: "사용자 제공 raw JSON이 공개 웹 조회보다 우선한다. 웹 조회는 누락·충돌·최신성 검산용 보조 소스다."
|
||||
xlsx_usage: "xlsx는 원본 감사 또는 JSON 재생성 확인에만 직접 사용한다. 일반 LLM 분석에서 xlsx 직접 파싱 금지."
|
||||
sheet_diet_policy:
|
||||
canonical_required: ["data_feed", "sector_flow", "macro", "event_risk", "core_satellite"]
|
||||
retained_support: ["settings", "account_snapshot", "sector_universe", "sector_flow_history", "etf_nav_manual", "universe", "monthly_history", "performance"]
|
||||
deprecated_sheets: ["positions", "chat_input", "etf_raw", "core_satellite_status", "orbit_gap", "asset_history"]
|
||||
transient_delete_when_complete: ["cs_chunk_N"]
|
||||
rule: "orbit_gap·asset_history는 monthly_history로 통합. etf_raw는 in-memory map 전환. core_satellite_status는 ScriptProperties 이전. cs_chunk_N은 병합 완료 후 삭제."
|
||||
last_cleanup:
|
||||
completed_at: "2026-05-18T09:50:00+09:00"
|
||||
removed_sheets: ["cs_chunk_0"]
|
||||
backup_file: "GatherTradingData.xlsx.20260518_095000.bak"
|
||||
validation_status: "PASS"
|
||||
|
||||
accepted_proposals:
|
||||
- proposal_id: "2026-05-18_SENIOR_VETERAN_UPGRADE"
|
||||
source_file: "proposals/2026-05-18_시니어_전문가_고도화_제안_SafeLanding_Factor.yaml"
|
||||
adopted_scope: "PARTIAL_MANIFEST_ONLY"
|
||||
adopted_items:
|
||||
- "Safe Landing Protocol은 spec/01_objective_profile.yaml에 이미 존재하므로 manifest에는 중복 본문을 복사하지 않는다."
|
||||
- "Factor Exposure Overload Brake는 동일 섹터 외 고베타·고모멘텀 과집중 차단 원칙으로 채택한다."
|
||||
- "Non-Linear Gap Risk Buffer는 이벤트 3거래일 전~당일 신규 진입 수량 산출 보수화 원칙으로 채택한다."
|
||||
- "Signal-Price Divergence Guard는 HTS 현재가와 JSON 기준 가격 3% 초과 괴리 시 BUY_BLOCKED 원칙으로 채택한다."
|
||||
rejected_or_deferred_items:
|
||||
- "canonical spec 세부 공식 수정은 별도 surgical update로만 수행한다."
|
||||
- "risk hard stop 완화, 목표 미달에 따른 risk_budget 강제 상향, 중복 SLP 본문 추가는 반영하지 않는다."
|
||||
duplicate_work_prevention: "이 proposal_id는 manifest 레벨에서 처리 완료. 동일 제안 재검토 시 미반영 세부 spec만 별도 target_file로 제안한다."
|
||||
completed_at: "2026-05-18T09:40:11+09:00"
|
||||
backup_file: "은퇴자산포트폴리오_20260518_094011.yaml.bak"
|
||||
validation_status: "PASS"
|
||||
|
||||
source_of_truth_order:
|
||||
1: "spec/00_execution_contract.yaml — master prohibitions, hard stops, capture ledger, order validation"
|
||||
2: "spec/risk/aggregate_risk.yaml — Total_Heat and portfolio hard stops"
|
||||
3: "spec/risk/circuit_breakers.yaml — circuit breakers and trading controls"
|
||||
4: "spec/risk/market_risk_cash.yaml — market risk score and cash rules"
|
||||
5: "spec/risk/portfolio_exposure.yaml — cash floor, duplicate exposure, target allocation"
|
||||
6: "spec/12_field_dictionary.yaml — canonical field names, aliases, units, missing policies"
|
||||
7: "spec/13_formula_registry.yaml — executable formula contracts for LLM calculation"
|
||||
7a: "spec/03_formulas/formula_registry.normalized.yaml — normalized formula registry index for governance validation"
|
||||
7b2: "spec/03_formulas/output_field_owner_ledger.yaml — output field owner ledger for writer precedence validation"
|
||||
7b: "spec/13b_harness_formulas.yaml — harness V4 formulas: TP_VALIDITY_CHECK_V1, PROFIT_LOCK_STAGE_CLASSIFIER_V1, REGIME_TRIM_WEIGHT_V1"
|
||||
7c: "spec/factor_lifecycle_registry.yaml — factor lifecycle status core/retired classification"
|
||||
8: "spec/14_raw_workbook_mapping.yaml — market raw JSON path/column mapping"
|
||||
9: "spec/15_account_snapshot_contract.yaml — image capture account/holding/cash contract"
|
||||
10: "spec/19_harness_contract.yaml — deterministic harness contract, lock semantics, sync validation"
|
||||
10b: "spec/20_harness_output_schema.yaml — mandatory numeric output schema; GAS coverage measurement baseline"
|
||||
10c: "spec/21_harness_governance_contract.yaml — harness governance 3-layer lock and release hardlocks"
|
||||
11: "spec/02_data_contract.yaml — source priority, data completeness, freshness, parsing rules"
|
||||
12: "spec/09_decision_flow.yaml — state-machine decision order"
|
||||
13: "spec/11_market_regime.yaml — canonical only for market regime classification"
|
||||
14: "spec/08_scoring_rules.yaml — rule_id based hard filters, scoring, grade thresholds"
|
||||
15: "spec/10_portfolio_rules.yaml — account routing, exposure, cash and contribution constraints"
|
||||
16: "spec/strategy/sector_model.yaml — sector and stock scoring model"
|
||||
17: "spec/strategy/entry_core.yaml — core entry gates"
|
||||
18: "spec/strategy/leader_scan.yaml — daily leader scan and anti-climax gate"
|
||||
19: "spec/strategy/staged_entry.yaml — staged entry and pullback reentry"
|
||||
20: "spec/05_position_sizing.yaml — volatility targeting, Bayesian/Kelly brake, integer quantity"
|
||||
21: "spec/exit/stop_loss.yaml — stop loss policy"
|
||||
22: "spec/exit/take_profit.yaml — take profit policy"
|
||||
23: "spec/exit/proactive_exit_radar.yaml — proactive exit radar"
|
||||
24: "spec/01_objective_profile.yaml — objective, account constraints, contribution policy"
|
||||
24b: "spec/property_invariants.yaml — property invariants specification"
|
||||
24c: "spec/anti_late_entry_contract.yaml — anti late entry contract"
|
||||
24d: "spec/profit_preservation_contract.yaml — profit preservation contract"
|
||||
24e: "spec/operating_cadence.yaml — weekly/monthly cadence specification"
|
||||
25: "spec/07_output_schema.yaml — recommendation grade, tables, HTS output schema"
|
||||
26: "schemas/output_schema.json — machine-readable output contract for LLM responses"
|
||||
27: "RetirementAssetPortfolioReportTemplate.yaml — human-readable report templates loaded only when report output is needed"
|
||||
28: "prompts/*.md — reusable prompt entrypoints for analysis/review"
|
||||
29: "examples/*.yaml and examples/examples.jsonl — examples are illustrative and never override rules"
|
||||
30: "tests/*.yaml — consistency checks for future edits"
|
||||
|
||||
load_sequence:
|
||||
STEP_1_always:
|
||||
files:
|
||||
- "RetirementAssetPortfolio.yaml"
|
||||
- "spec/00_execution_contract.yaml"
|
||||
- "spec/risk/aggregate_risk.yaml"
|
||||
- "spec/risk/circuit_breakers.yaml"
|
||||
- "spec/risk/market_risk_cash.yaml"
|
||||
- "spec/risk/portfolio_exposure.yaml"
|
||||
- "spec/risk/factor_risk.yaml"
|
||||
- "spec/12_field_dictionary.yaml"
|
||||
- "spec/13_formula_registry.yaml"
|
||||
- "spec/03_formulas/formula_registry.normalized.yaml"
|
||||
- "spec/03_formulas/output_field_owner_ledger.yaml"
|
||||
- "spec/factor_lifecycle_registry.yaml"
|
||||
- "spec/13b_harness_formulas.yaml"
|
||||
- "spec/14_raw_workbook_mapping.yaml"
|
||||
- "spec/15_account_snapshot_contract.yaml"
|
||||
- "spec/19_harness_contract.yaml"
|
||||
- "spec/20_harness_output_schema.yaml"
|
||||
- "spec/21_harness_governance_contract.yaml"
|
||||
- "spec/40_final_decision_packet_contract.yaml"
|
||||
- "spec/41_release_dag.yaml"
|
||||
- "spec/43_quant_factor_taxonomy.yaml"
|
||||
- "spec/45_number_provenance_contract.yaml"
|
||||
- "spec/46_low_capability_execution_pack.yaml"
|
||||
- "spec/47_packaging_policy.yaml"
|
||||
- "spec/property_invariants.yaml"
|
||||
- "spec/anti_late_entry_contract.yaml"
|
||||
- "spec/profit_preservation_contract.yaml"
|
||||
- "spec/operating_cadence.yaml"
|
||||
- "spec/formula_lifecycle_index.yaml"
|
||||
- "spec/release/low_capability_context_aliases.yaml"
|
||||
- "spec/release/version_retirement_policy.yaml"
|
||||
- "spec/48_module_io_contract_registry.yaml"
|
||||
- "spec/49_refactor_methodology_contract.yaml"
|
||||
- "spec/51_formula_lifecycle_registry.yaml"
|
||||
- "Temp/final_decision_packet_v4.json"
|
||||
- "spec/02_data_contract.yaml"
|
||||
- "spec/01_objective_profile.yaml"
|
||||
purpose: "위험 차단, 데이터 사용 가능성, 계좌 제약을 먼저 확정한다."
|
||||
STEP_2_for_buy_sell_decision:
|
||||
files:
|
||||
- "spec/09_decision_flow.yaml"
|
||||
- "spec/11_market_regime.yaml"
|
||||
- "spec/08_scoring_rules.yaml"
|
||||
- "spec/10_portfolio_rules.yaml"
|
||||
- "spec/strategy/sector_model.yaml"
|
||||
- "spec/strategy/entry_core.yaml"
|
||||
- "spec/strategy/leader_scan.yaml"
|
||||
- "spec/strategy/staged_entry.yaml"
|
||||
- "spec/05_position_sizing.yaml"
|
||||
- "spec/exit/stop_loss.yaml"
|
||||
- "spec/exit/take_profit.yaml"
|
||||
- "spec/exit/proactive_exit_radar.yaml"
|
||||
purpose: "진입·청산·수량 산출을 결정한다."
|
||||
STEP_3_for_output:
|
||||
files:
|
||||
- "spec/07_output_schema.yaml"
|
||||
- "schemas/output_schema.json"
|
||||
- "RetirementAssetPortfolioReportTemplate.yaml"
|
||||
- "spec/40_final_decision_packet_contract.yaml"
|
||||
- "spec/46_low_capability_execution_pack.yaml"
|
||||
- "Temp/final_decision_packet_v4.json"
|
||||
- "Temp/final_context_for_llm_v4.yaml"
|
||||
purpose: "JSON 출력 계약, HTS 입력표, 상세 보고서 양식을 확정한다. 사용자 출력 보고서의 모든 상태값·레이블은 spec/07_output_schema.yaml의 korean_display_labels 매핑을 적용해 한글로 표시한다."
|
||||
STEP_4_for_validation:
|
||||
files:
|
||||
- "tests/strategy_tests.yaml"
|
||||
- "examples/*.yaml"
|
||||
- "examples/examples.jsonl"
|
||||
purpose: "규칙 일관성 점검과 예시 대조에 사용한다."
|
||||
STEP_5_for_prompt_entrypoints:
|
||||
files:
|
||||
- "prompts/analysis_prompt.md"
|
||||
- "prompts/review_prompt.md"
|
||||
purpose: "반복 분석과 리뷰 작업의 시작 프롬프트로 사용한다."
|
||||
|
||||
spec_files:
|
||||
execution_contract: "spec/00_execution_contract.yaml"
|
||||
objective_profile: "spec/01_objective_profile.yaml"
|
||||
data_contract: "spec/02_data_contract.yaml"
|
||||
field_dictionary: "spec/12_field_dictionary.yaml"
|
||||
formula_registry: "spec/13_formula_registry.yaml"
|
||||
formula_registry_normalized: "spec/03_formulas/formula_registry.normalized.yaml"
|
||||
output_field_owner_ledger: "spec/03_formulas/output_field_owner_ledger.yaml"
|
||||
formula_domain_manifest: "spec/formulas/domains/manifest.yaml"
|
||||
formula_domain_risk: "spec/formulas/domains/risk.yaml"
|
||||
formula_domain_entry: "spec/formulas/domains/entry.yaml"
|
||||
formula_domain_exit: "spec/formulas/domains/exit.yaml"
|
||||
formula_domain_cash: "spec/formulas/domains/cash.yaml"
|
||||
formula_domain_portfolio: "spec/formulas/domains/portfolio.yaml"
|
||||
formula_domain_reporting: "spec/formulas/domains/reporting.yaml"
|
||||
formula_domain_fundamental: "spec/formulas/domains/fundamental.yaml"
|
||||
formula_domain_smart_money: "spec/formulas/domains/smart_money.yaml"
|
||||
formula_domain_macro: "spec/formulas/domains/macro.yaml"
|
||||
harness_formula_registry: "spec/13b_harness_formulas.yaml"
|
||||
raw_workbook_mapping: "spec/14_raw_workbook_mapping.yaml"
|
||||
account_snapshot_contract: "spec/15_account_snapshot_contract.yaml"
|
||||
harness_contract: "spec/19_harness_contract.yaml"
|
||||
harness_governance_contract: "spec/21_harness_governance_contract.yaml"
|
||||
pipeline_runtime_contract: "spec/22_pipeline_runtime_contract.yaml"
|
||||
low_capability_llm_pipeline_todo: "spec/23_low_capability_llm_pipeline_todo.yaml"
|
||||
strategy_hardening_todo_v1: "spec/24_strategy_hardening_todo_v1.yaml"
|
||||
canonical_metrics_registry: "spec/25_canonical_metrics_registry.yaml"
|
||||
behavioral_coverage_contract: "spec/26_behavioral_coverage_contract.yaml"
|
||||
calibration_registry: "spec/calibration_registry.yaml"
|
||||
formula_golden_cases_v2: "spec/formula_golden_cases_v2.yaml"
|
||||
formula_golden_cases_v3: "spec/formula_golden_cases_v3.yaml"
|
||||
formula_golden_cases_v4: "spec/formula_golden_cases_v4.yaml"
|
||||
formula_golden_cases_nf: "spec/formula_golden_cases_nf.yaml"
|
||||
canonical_artifact_resolver_v1: "spec/32_canonical_artifact_resolver.yaml"
|
||||
execution_precedence_lock_v1: "spec/33_execution_precedence_lock.yaml"
|
||||
architecture_boundaries_v1: "spec/34_architecture_boundaries.yaml"
|
||||
rule_lifecycle_governance_v3: "spec/35_rule_lifecycle_governance_v3.yaml"
|
||||
gas_thin_adapter_policy: "spec/39_gas_thin_adapter_policy.yaml"
|
||||
goal_risk_budget_harness_v1: "spec/36_goal_risk_budget_harness.yaml"
|
||||
evaluation_dashboard_v1: "spec/37_evaluation_dashboard_contract.yaml"
|
||||
bch_calibration_runbook: "spec/27_bch_calibration_runbook.yaml"
|
||||
imputed_data_exposure_contract: "spec/28_imputed_data_exposure_contract.yaml"
|
||||
backtest_harness_contract: "spec/29_backtest_harness_contract.yaml"
|
||||
completion_criteria_contract: "spec/30_completion_criteria_contract.yaml"
|
||||
semiconductor_concentration_policy: "spec/strategy/semiconductor_concentration_policy.yaml"
|
||||
strategy_execution_lock_policy: "spec/strategy_execution_lock_policy.yaml"
|
||||
low_capability_llm_response_contract: "spec/31_low_capability_llm_response_contract.yaml"
|
||||
final_decision_packet_contract_v4: "spec/40_final_decision_packet_contract.yaml"
|
||||
release_dag_v1: "spec/41_release_dag.yaml"
|
||||
quant_factor_taxonomy_v1: "spec/43_quant_factor_taxonomy.yaml"
|
||||
live_replay_separation_v2: "spec/44_live_replay_separation.yaml"
|
||||
number_provenance_contract_v1: "spec/45_number_provenance_contract.yaml"
|
||||
low_capability_execution_pack_v1: "spec/46_low_capability_execution_pack.yaml"
|
||||
packaging_policy_v1: "spec/47_packaging_policy.yaml"
|
||||
property_invariants: "spec/property_invariants.yaml"
|
||||
anti_late_entry_contract: "spec/anti_late_entry_contract.yaml"
|
||||
profit_preservation_contract: "spec/profit_preservation_contract.yaml"
|
||||
operating_cadence: "spec/operating_cadence.yaml"
|
||||
formula_lifecycle_index: "spec/formula_lifecycle_index.yaml"
|
||||
low_capability_context_aliases: "spec/release/low_capability_context_aliases.yaml"
|
||||
version_retirement_policy: "spec/release/version_retirement_policy.yaml"
|
||||
repository_entropy_budget_v1: "spec/release/repository_entropy_budget.yaml"
|
||||
execution_authority_matrix_v2: "spec/execution_authority_matrix_v2.yaml"
|
||||
entry_freshness_score_v1: "spec/strategy/entry_freshness_score_v1.yaml"
|
||||
data_gaps_roadmap: "spec/16_data_gaps_roadmap.yaml"
|
||||
performance_contract: "spec/17_performance_contract.yaml"
|
||||
settings_contract: "spec/18_settings_contract.yaml"
|
||||
risk_policy_index: "spec/03_risk_policy.yaml"
|
||||
risk_control_index: "spec/risk/risk_control.yaml"
|
||||
aggregate_risk: "spec/risk/aggregate_risk.yaml"
|
||||
circuit_breakers: "spec/risk/circuit_breakers.yaml"
|
||||
market_risk_cash: "spec/risk/market_risk_cash.yaml"
|
||||
portfolio_exposure: "spec/risk/portfolio_exposure.yaml"
|
||||
risk_quality_control: "spec/risk/quality_control.yaml"
|
||||
factor_risk: "spec/risk/factor_risk.yaml"
|
||||
strategy_rules_index: "spec/04_strategy_rules.yaml"
|
||||
sector_model: "spec/strategy/sector_model.yaml"
|
||||
entry_gates_index: "spec/strategy/entry_gates.yaml"
|
||||
entry_core: "spec/strategy/entry_core.yaml"
|
||||
leader_scan: "spec/strategy/leader_scan.yaml"
|
||||
staged_entry: "spec/strategy/staged_entry.yaml"
|
||||
strategy_discovery: "spec/strategy/discovery.yaml"
|
||||
stock_model: "spec/strategy/stock_model.yaml"
|
||||
action_matrix: "spec/strategy/action_matrix.yaml"
|
||||
rebalancing_trigger: "spec/strategy/rebalancing_trigger.yaml"
|
||||
fundamental_quality: "spec/strategy/fundamental_quality.yaml"
|
||||
fundamental_quality_v2: "spec/strategy/fundamental_quality_v2.yaml"
|
||||
fundamental_quality_v3: "spec/strategy/fundamental_quality_v3.yaml"
|
||||
fundamental_and_horizon_gate_v1: "spec/strategy/fundamental_and_horizon_gate_v1.yaml"
|
||||
horizon_allocation_v1: "spec/strategy/horizon_allocation_v1.yaml"
|
||||
smart_money_liquidity_gate_v1: "spec/strategy/smart_money_liquidity_gate_v1.yaml"
|
||||
anti_late_entry_pullback_gate_v4: "spec/strategy/anti_late_entry_pullback_gate_v4.yaml"
|
||||
pre_distribution_early_warning_v3: "spec/strategy/pre_distribution_early_warning_v3.yaml"
|
||||
anti_late_entry_pullback_gate_v5: "spec/strategy/anti_late_entry_pullback_gate_v5.yaml"
|
||||
pre_distribution_early_warning_v4: "spec/strategy/pre_distribution_early_warning_v4.yaml"
|
||||
macro_event_synchronizer_v2: "spec/strategy/macro_event_synchronizer_v2.yaml"
|
||||
value_preserving_cash_raise_optimizer_v7: "spec/exit/value_preserving_cash_raise_optimizer_v7.yaml"
|
||||
predictive_alpha_dialectic_v1: "spec/strategy/predictive_alpha_dialectic_v1.yaml"
|
||||
field_dictionary_strict: "spec/fields/field_dictionary.yaml"
|
||||
data_quality_expectations: "spec/data_quality/expectations.yaml"
|
||||
decision_graph_dag: "spec/routing/decision_graph.yaml"
|
||||
shadow_ledger_contract: "spec/outputs/shadow_ledger_contract.yaml"
|
||||
renderer_contract: "spec/render/renderer_contract.yaml"
|
||||
release_train: "spec/release/release_train.yaml"
|
||||
predictive_alpha_dialectic_v2: "spec/strategy/predictive_alpha_dialectic_v2.yaml"
|
||||
routing_trace_v2: "spec/routing_trace_v2.yaml"
|
||||
market_regime: "spec/11_market_regime.yaml"
|
||||
scoring_rules: "spec/08_scoring_rules.yaml"
|
||||
decision_flow: "spec/09_decision_flow.yaml"
|
||||
portfolio_rules: "spec/10_portfolio_rules.yaml"
|
||||
position_sizing: "spec/05_position_sizing.yaml"
|
||||
exit_policy_index: "spec/06_exit_policy.yaml"
|
||||
stop_loss: "spec/exit/stop_loss.yaml"
|
||||
take_profit: "spec/exit/take_profit.yaml"
|
||||
proactive_exit_radar: "spec/exit/proactive_exit_radar.yaml"
|
||||
event_response: "spec/exit/event_response.yaml"
|
||||
position_review: "spec/exit/position_review.yaml"
|
||||
dynamic_value_preservation_sell_v3: "spec/exit/dynamic_value_preservation_sell_v3.yaml"
|
||||
output_schema: "spec/07_output_schema.yaml"
|
||||
machine_output_schema: "schemas/output_schema.json"
|
||||
report_templates: "RetirementAssetPortfolioReportTemplate.yaml"
|
||||
prompts: "prompts/*.md"
|
||||
examples: "examples/*.yaml"
|
||||
examples_jsonl: "examples/examples.jsonl"
|
||||
tests: "tests/*.yaml"
|
||||
proposals: "proposals/*.yaml"
|
||||
factor_lifecycle_registry: "spec/factor_lifecycle_registry.yaml"
|
||||
# ── H001~H008 신규 하네스 계약 (P5-T01, 2026-06-10) ──────────────────────
|
||||
decision_trace_replay_contract: "spec/52_decision_trace_replay_contract.yaml"
|
||||
factor_conflict_matrix: "spec/53_factor_conflict_matrix.yaml"
|
||||
temporal_data_integrity: "spec/54_temporal_data_integrity.yaml"
|
||||
execution_simulator_contract: "spec/55_execution_simulator_contract.yaml"
|
||||
renderer_copy_only_contract: "spec/56_renderer_copy_only_contract.yaml"
|
||||
shadow_promotion_scorecard: "spec/57_shadow_promotion_scorecard.yaml"
|
||||
llm_determinism_contract: "spec/58_llm_determinism_contract.yaml"
|
||||
|
||||
governance:
|
||||
ownership_map: "spec/ownership_map.yaml"
|
||||
aliases: "spec/aliases.yaml"
|
||||
xref_matrix: "spec/xref_matrix.yaml"
|
||||
future_split_indexes:
|
||||
risk: "spec/risk/README.md"
|
||||
strategy: "spec/strategy/README.md"
|
||||
active_canonical_splits:
|
||||
risk:
|
||||
- "spec/risk/aggregate_risk.yaml"
|
||||
- "spec/risk/circuit_breakers.yaml"
|
||||
- "spec/risk/market_risk_cash.yaml"
|
||||
- "spec/risk/portfolio_exposure.yaml"
|
||||
- "spec/risk/risk_control.yaml"
|
||||
- "spec/risk/quality_control.yaml"
|
||||
exit:
|
||||
- "spec/exit/stop_loss.yaml"
|
||||
- "spec/exit/take_profit.yaml"
|
||||
- "spec/exit/proactive_exit_radar.yaml"
|
||||
- "spec/exit/event_response.yaml"
|
||||
- "spec/exit/position_review.yaml"
|
||||
- "spec/exit/dynamic_value_preservation_sell_v3.yaml"
|
||||
strategy:
|
||||
- "spec/strategy/sector_model.yaml"
|
||||
- "spec/strategy/entry_gates.yaml"
|
||||
- "spec/strategy/entry_core.yaml"
|
||||
- "spec/strategy/leader_scan.yaml"
|
||||
- "spec/strategy/staged_entry.yaml"
|
||||
- "spec/strategy/discovery.yaml"
|
||||
- "spec/strategy/stock_model.yaml"
|
||||
- "spec/strategy/action_matrix.yaml"
|
||||
- "spec/strategy/rebalancing_trigger.yaml"
|
||||
- "spec/strategy/fundamental_and_horizon_gate_v1.yaml"
|
||||
- "spec/strategy/horizon_allocation_v1.yaml"
|
||||
- "spec/strategy/smart_money_liquidity_gate_v1.yaml"
|
||||
- "spec/strategy/predictive_alpha_dialectic_v1.yaml"
|
||||
|
||||
conflict_resolution:
|
||||
priority: "상위 source_of_truth_order가 하위 규칙보다 항상 우선한다."
|
||||
risk_vs_strategy: "리스크 차단 규칙과 매수/증액 규칙이 충돌하면 리스크 차단 규칙만 적용한다."
|
||||
data_vs_decision: "데이터 완성도·최신성 미달이면 등급·수량·주문 결론을 보류한다."
|
||||
table_vs_text: "표/수치/공식과 서술형 설명이 충돌하면 표/수치/공식을 우선한다."
|
||||
examples_vs_rules: "examples는 판단 예시이며 어떤 경우에도 spec 규칙을 override하지 않는다."
|
||||
tie_rule: "동급 규칙이 충돌하면 보류하고 필요한 추가 데이터와 다음 확인 출처를 출력한다."
|
||||
secular_leader_vs_safety: >
|
||||
SECULAR_LEADER_RISK_ON 활성 중 EVENT_SHOCK 또는 RISK_OFF 발동 시
|
||||
공격 모드 즉시 비활성. 공격 모드는 안전·리스크 차단 모드를 절대 우선하지 않는다.
|
||||
우선순위: EVENT_SHOCK > RISK_OFF > SECULAR_LEADER_RISK_ON > LEADER_CONCENTRATION > NEUTRAL > RISK_ON.
|
||||
참조: spec/11_market_regime.yaml.SECULAR_LEADER_RISK_ON.priority_over_lower_states.
|
||||
goal_protection_vs_orbit_gap: >
|
||||
Safe Landing Protocol, hard stop, cash_floor, Total_Heat, 데이터 완성도 차단은
|
||||
orbit_gap 또는 목표 미달 기반 공격 슬롯 확대보다 항상 우선한다.
|
||||
목표 미달·필요수익률 상승은 risk_budget 상향 또는 데이터 게이트 완화 사유가 아니다.
|
||||
목표자산 90% 이상 접근 시 spec/01_objective_profile.yaml.safe_landing_protocol을 우선 확인한다.
|
||||
|
||||
non_negotiable_principles:
|
||||
- "spec/00_execution_contract.yaml의 master_prohibitions P1~P5가 모든 규칙보다 우선한다."
|
||||
- "보유수량 미확인 상태에서 매도수량 숫자 기재 금지."
|
||||
- "ATR20 미확인 상태에서 정수 매수수량 산출 금지."
|
||||
- "Total_Heat, cash_floor, hard stop을 우회하는 공격 슬롯 금지."
|
||||
- "Flow_Rows<20이면 20D 수급 기반 A등급·즉시매수 금지."
|
||||
- "매수 제안은 지정가·수량·손절가·손절수량·익절가·익절수량 세트가 모두 있을 때만 유효."
|
||||
- "소수점 매수 금지. 모든 주문 수량은 정수."
|
||||
- "SECULAR_LEADER_RISK_ON은 spec/11_market_regime.yaml 5개 조건 동시 충족 시에만 활성화된다. 활성 중에도 master_prohibitions P1~P5, Total_Heat hard stop, ATR20, 보유수량 확인, cash_floor 제약은 그대로 적용된다. 이 모드는 safety gate를 열지 않는다."
|
||||
- "목표자산 90% 이상 접근 시 Safe Landing Protocol이 orbit_gap 공격 슬롯 확대, 위성 비중 확대, 신규 탐색매수보다 우선한다."
|
||||
- "동일 섹터가 아니어도 고베타·고모멘텀 팩터가 과집중이면 신규 고베타 종목 매수를 보류하고 저변동성·현금·단기채 대안을 우선 검토한다."
|
||||
- "이벤트 3거래일 전부터 당일까지 신규 진입은 갭 리스크 버퍼를 수량 산출에 반영하지 않으면 정수 수량을 확정하지 않는다."
|
||||
- "HTS 현재가와 JSON 기준 가격의 괴리율이 3%를 초과하면 BUY_BLOCKED 처리하고 JSON 재생성 또는 가격 출처 재검산을 먼저 요구한다."
|
||||
|
||||
surgical_update_policy:
|
||||
rule: "규칙 변경은 전체 재작성 금지. target_file, target_path, change_type, rationale를 남기는 최소 침습 변경만 허용한다."
|
||||
required_fields: ["target_file", "target_path", "change_type", "rationale", "before", "after", "validation"]
|
||||
prohibited_changes:
|
||||
- "master prohibitions 삭제"
|
||||
- "risk hard stop 완화"
|
||||
- "데이터 누락 상태에서 BUY 허용"
|
||||
- "목표수익률 압박을 이유로 risk_budget 강제 상향"
|
||||
- "SECULAR_LEADER_RISK_ON 활성을 이유로 master_prohibitions·cash_floor·Total_Heat·ATR20 제약 완화 금지"
|
||||
completed_update_markers:
|
||||
required_fields: ["proposal_id", "adopted_scope", "completed_at", "backup_file", "validation_status"]
|
||||
rule: "외부 제안 반영 후 accepted_proposals에 완료 표기를 남겨 동일 proposal_id 중복 반영을 방지한다."
|
||||
validation_status_values: ["PENDING", "PASS", "FAIL", "PARTIAL"]
|
||||
|
||||
output_contract:
|
||||
machine_readable_schema: "schemas/output_schema.json"
|
||||
json_schema_version: "2026-05-15-F6-compat-output"
|
||||
required_first_outputs:
|
||||
- "사용 데이터 기준시각과 출처"
|
||||
- "capture_read_ledger 판독 원장"
|
||||
- "데이터 완성도 매트릭스"
|
||||
- "4단계 주문 검산 결과"
|
||||
- "decision_trace 판단 추적표"
|
||||
- "리스크/현금/Total_Heat 차단 여부"
|
||||
- "HTS 입력 가능 주문표 또는 산출금지 사유"
|
||||
- "학습형 해설은 요청 시 후반부 market_context_learning_note로만 출력"
|
||||
report_template_file: "RetirementAssetPortfolioReportTemplate.yaml"
|
||||
|
||||
bundle_outputs:
|
||||
full_bundle: "dist/retirement_portfolio_bundle.yaml"
|
||||
compact_bundle: "dist/retirement_portfolio_compact.yaml"
|
||||
ultra_compact_bundle: "dist/retirement_portfolio_ultra_compact.yaml"
|
||||
bundle_selection_policy:
|
||||
full_bundle: "문서 정합성 리뷰, 리팩토링, 규칙 충돌 점검에 사용한다."
|
||||
compact_bundle: "일반 분석에서 충분한 컨텍스트가 필요할 때 사용한다."
|
||||
ultra_compact_bundle: "토큰 비용을 줄여야 하거나 반복 실행용 LLM 입력으로 사용한다. 상세 보고서 양식과 illustrative examples는 포함하지 않는다."
|
||||
|
||||
bundle_profiles:
|
||||
compact:
|
||||
output: "dist/retirement_portfolio_compact.yaml"
|
||||
purpose: "분석에 필요한 canonical 핵심 문서만 묶은 축약 LLM 입력용 합본"
|
||||
files:
|
||||
- "RetirementAssetPortfolio.yaml"
|
||||
- "AGENTS.md"
|
||||
- "spec/00_execution_contract.yaml"
|
||||
- "spec/02_data_contract.yaml"
|
||||
- "spec/risk/aggregate_risk.yaml"
|
||||
- "spec/risk/circuit_breakers.yaml"
|
||||
- "spec/risk/market_risk_cash.yaml"
|
||||
- "spec/risk/portfolio_exposure.yaml"
|
||||
- "spec/12_field_dictionary.yaml"
|
||||
- "spec/13_formula_registry.yaml"
|
||||
- "spec/13b_harness_formulas.yaml"
|
||||
- "spec/14_raw_workbook_mapping.yaml"
|
||||
- "spec/15_account_snapshot_contract.yaml"
|
||||
- "spec/09_decision_flow.yaml"
|
||||
- "spec/11_market_regime.yaml"
|
||||
- "spec/08_scoring_rules.yaml"
|
||||
- "spec/10_portfolio_rules.yaml"
|
||||
- "spec/strategy/sector_model.yaml"
|
||||
- "spec/strategy/entry_core.yaml"
|
||||
- "spec/strategy/leader_scan.yaml"
|
||||
- "spec/strategy/staged_entry.yaml"
|
||||
- "spec/05_position_sizing.yaml"
|
||||
- "spec/exit/stop_loss.yaml"
|
||||
- "spec/exit/take_profit.yaml"
|
||||
- "spec/exit/proactive_exit_radar.yaml"
|
||||
- "spec/40_final_decision_packet_contract.yaml"
|
||||
- "spec/41_release_dag.yaml"
|
||||
- "spec/43_quant_factor_taxonomy.yaml"
|
||||
- "spec/45_number_provenance_contract.yaml"
|
||||
- "spec/46_low_capability_execution_pack.yaml"
|
||||
- "spec/47_packaging_policy.yaml"
|
||||
- "spec/property_invariants.yaml"
|
||||
- "spec/anti_late_entry_contract.yaml"
|
||||
- "spec/profit_preservation_contract.yaml"
|
||||
- "spec/operating_cadence.yaml"
|
||||
- "spec/formula_lifecycle_index.yaml"
|
||||
- "spec/release/low_capability_context_aliases.yaml"
|
||||
- "spec/release/version_retirement_policy.yaml"
|
||||
- "spec/48_module_io_contract_registry.yaml"
|
||||
- "spec/49_refactor_methodology_contract.yaml"
|
||||
- "spec/51_formula_lifecycle_registry.yaml"
|
||||
- "Temp/final_decision_packet_v4.json"
|
||||
- "schemas/output_schema.json"
|
||||
ultra_compact:
|
||||
output: "dist/retirement_portfolio_ultra_compact.yaml"
|
||||
purpose: "실행계약, 데이터, 리스크, 상태머신, 수량, 청산, 출력계약만 담은 최소 LLM 입력용 합본"
|
||||
files:
|
||||
- "RetirementAssetPortfolio.yaml"
|
||||
- "AGENTS.md"
|
||||
- "spec/00_execution_contract.yaml"
|
||||
- "spec/02_data_contract.yaml"
|
||||
- "spec/risk/aggregate_risk.yaml"
|
||||
- "spec/risk/circuit_breakers.yaml"
|
||||
- "spec/risk/market_risk_cash.yaml"
|
||||
- "spec/risk/portfolio_exposure.yaml"
|
||||
- "spec/12_field_dictionary.yaml"
|
||||
- "spec/13_formula_registry.yaml"
|
||||
- "spec/13b_harness_formulas.yaml"
|
||||
- "spec/14_raw_workbook_mapping.yaml"
|
||||
- "spec/15_account_snapshot_contract.yaml"
|
||||
- "spec/09_decision_flow.yaml"
|
||||
- "spec/08_scoring_rules.yaml"
|
||||
- "spec/05_position_sizing.yaml"
|
||||
- "spec/exit/stop_loss.yaml"
|
||||
- "spec/exit/take_profit.yaml"
|
||||
- "spec/exit/proactive_exit_radar.yaml"
|
||||
- "spec/40_final_decision_packet_contract.yaml"
|
||||
- "spec/41_release_dag.yaml"
|
||||
- "spec/43_quant_factor_taxonomy.yaml"
|
||||
- "spec/45_number_provenance_contract.yaml"
|
||||
- "spec/46_low_capability_execution_pack.yaml"
|
||||
- "spec/47_packaging_policy.yaml"
|
||||
- "spec/property_invariants.yaml"
|
||||
- "spec/anti_late_entry_contract.yaml"
|
||||
- "spec/profit_preservation_contract.yaml"
|
||||
- "spec/operating_cadence.yaml"
|
||||
- "spec/formula_lifecycle_index.yaml"
|
||||
- "spec/release/low_capability_context_aliases.yaml"
|
||||
- "spec/release/version_retirement_policy.yaml"
|
||||
- "spec/48_module_io_contract_registry.yaml"
|
||||
- "spec/49_refactor_methodology_contract.yaml"
|
||||
- "spec/51_formula_lifecycle_registry.yaml"
|
||||
- "Temp/final_decision_packet_v4.json"
|
||||
- "schemas/output_schema.json"
|
||||
@@ -0,0 +1,665 @@
|
||||
meta:
|
||||
title: "은퇴자산포트폴리오 — 상세 보고서 양식"
|
||||
parent_file: "RetirementAssetPortfolio.yaml"
|
||||
version: "2026-05-15-G1"
|
||||
purpose: >
|
||||
보고서 출력 시에만 로드하는 상세 양식 파일.
|
||||
투자 판단(분석·수량산출)만 필요한 세션에서는 이 파일 없이 메인 파일만으로 충분.
|
||||
load_instruction: "메인 manifest의 load_sequence.STEP_3_for_output 및 spec_files.report_templates 참조."
|
||||
|
||||
output_format_templates:
|
||||
|
||||
rendering_contract:
|
||||
purpose: "사람용 보고서를 표 중심으로 렌더링하기 위한 상위 양식 계약."
|
||||
language_rendering:
|
||||
default_locale: "ko-KR"
|
||||
section_title_korean_first: true
|
||||
status_label_map_source: "spec/07_output_schema.yaml:korean_display_labels"
|
||||
prefer_korean_reason_text: true
|
||||
preserve_english_only_for:
|
||||
- "공식_ID"
|
||||
- "종목코드"
|
||||
- "파일경로"
|
||||
- "명령어"
|
||||
- "JSON key"
|
||||
prohibit_direct_english_status:
|
||||
- "PASS"
|
||||
- "FAIL"
|
||||
- "BLOCKED"
|
||||
- "ACTIVE"
|
||||
- "INACTIVE"
|
||||
- "BUY"
|
||||
- "SELL"
|
||||
- "TRIM"
|
||||
required_sequence:
|
||||
- "routing_serving_trace"
|
||||
- "QEH_AUDIT_BLOCK"
|
||||
- "capture_read_ledger"
|
||||
- "data_completeness_matrix"
|
||||
- "backdata_feature_bank_table"
|
||||
- "benchmark_relative_harness_table"
|
||||
- "alpha_lead_table"
|
||||
- "anti_distribution_table"
|
||||
- "profit_preservation_table"
|
||||
- "smart_cash_raise_table"
|
||||
- "execution_quality_table"
|
||||
- "proposal_reference_sheet"
|
||||
# ── [2026-05-20_HARNESS_V5] 신규 필수 섹션 ──────────────────────────────
|
||||
- "breakout_quality_gate_table"
|
||||
- "anti_whipsaw_gate_table"
|
||||
- "smart_cash_raise_v2_table"
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- "order_quantity_4stage_gate"
|
||||
- "decision_trace_table"
|
||||
- "sell_priority_decision_table 또는 발동조건 미충족 사유"
|
||||
- "current_holdings_analysis_report_template 또는 발동조건 미충족 사유"
|
||||
- "proposal_reference_sheet 또는 제안 산출 불가 사유"
|
||||
- "concise_hts_input_sheet 또는 산출금지 사유"
|
||||
- "reference_price_ledger 또는 WATCH 없음"
|
||||
- "immediate_execution_playbook 또는 산출금지 사유"
|
||||
- "market_context_learning_note 또는 생략 사유"
|
||||
- "core_satellite_timing_gate_table"
|
||||
- "engine_feedback_loop_report"
|
||||
- "prediction_evaluation_improvement_report"
|
||||
blocked_report_rule:
|
||||
id: "BLOCKED_REPORT_V5"
|
||||
trigger: "required_sequence의 필수 섹션이 하나라도 누락된 경우"
|
||||
action: "해당 섹션 이후의 모든 주문표·HTS 입력 시트를 BLOCKED_REPORT로 처리"
|
||||
hard_lock_sections:
|
||||
- section: "breakout_quality_gate_table"
|
||||
missing_action: "신규 BUY 주문표 전체 BLOCKED_BREAKOUT_GATE_MISSING"
|
||||
- section: "anti_whipsaw_gate_table"
|
||||
missing_action: "SELL/TRIM 주문표 전체 BLOCKED_WHIPSAW_GATE_MISSING"
|
||||
- section: "smart_cash_raise_v2_table"
|
||||
missing_action: "현금확보 매도 주문표 BLOCKED_CASH_ROUTE_MISSING"
|
||||
prose_rule:
|
||||
allowed: "각 표의 근거·매도사유·다음확인사항 칸 안의 짧은 문장"
|
||||
prohibited: "서론/본론/결론형 산문, 임의 제목, 표 밖 수량·현금·평단 근거 서술"
|
||||
enum_rule:
|
||||
order_type: ["BUY", "SELL", "STOP_LOSS", "TAKE_PROFIT", "TRAILING_STOP", "HOLD", "WATCH"]
|
||||
mode: ["lead", "lag", "hybrid", "none"]
|
||||
validation_status: ["PASS", "BLOCKED", "INSUFFICIENT_DATA", "MANUAL_CHECK_REQUIRED"]
|
||||
human_label_rule: "사용자 보고서에는 spec/07_output_schema.yaml:korean_display_labels를 적용하되, 원 enum에서 벗어난 새 용어를 만들지 않는다."
|
||||
terminology_rule:
|
||||
prohibited_order_terms: ["부분감액", "1차 감액", "부분정리", "전량", "전량매도"]
|
||||
replacement_rule: "주문구분은 SELL/TRIM 등 canonical action으로, 수량은 확인된 정수 수량으로 분리한다."
|
||||
note: "'전량'은 모드가 아니다. 보유수량 검산 후 SELL 수량이 현재보유수량과 같을 때만 근거 칸에서 설명 가능하다."
|
||||
learning_note_rule:
|
||||
location: "주문 검산·HTS 주문표·보유주 진단 이후에만 배치한다."
|
||||
role: "사용자 이해를 돕는 해설이며 주문 검증, 수량 산출, validation_status를 대체하지 않는다."
|
||||
prohibition:
|
||||
- "학습 해설에서 신규 주문수량·지정가·손절가·익절가·현금 숫자 생성 금지"
|
||||
- "표에서 검증되지 않은 계좌·보유·현금 숫자 인용 금지"
|
||||
- "거시 설명만으로 BUY/SELL/TRIM/ROTATE 결론 변경 금지"
|
||||
|
||||
routing_serving_trace_report:
|
||||
purpose: "요청 라우팅·번들·프롬프트·JSON 검증 상태를 QEH_AUDIT_BLOCK 이전에 고정하는 서빙 원장."
|
||||
columns: ["항목", "확정값", "근거", "검산상태", "차단사유"]
|
||||
required_rows:
|
||||
- "request_route"
|
||||
- "bundle_selected"
|
||||
- "prompt_entrypoint"
|
||||
- "json_validation_status"
|
||||
- "capture_required"
|
||||
- "cash_ledger_basis"
|
||||
fill_rule:
|
||||
- "RetirementAssetPortfolio.yaml manifest와 DATA_SOURCE_ROUTING 결과를 사용한다."
|
||||
- "JSON 검증 실패 또는 bundle/entrypoint 미확정이면 QEH_AUDIT_BLOCK 이후 주문표는 BLOCKED_REPORT로 제한한다."
|
||||
- "cash_ledger_basis는 settlement_cash_d2_krw 원칙을 명시한다."
|
||||
|
||||
QEH_AUDIT_BLOCK_report:
|
||||
purpose: "하네스 공식 검산 표. 주문표·판단표보다 먼저 출력한다."
|
||||
columns: ["공식_ID", "사용입력", "하네스값", "LLM_변경여부", "판정", "차단/허용 액션"]
|
||||
required_formulas: ["TOTAL_HEAT_V1", "CASH_RATIOS_V1", "SELL_PRIORITY_V1"]
|
||||
optional_formulas: ["GOAL_RETIREMENT_V1", "CASH_SHORTFALL_V1"]
|
||||
fill_rule:
|
||||
- "data._harness_context 값은 재계산하지 않고 그대로 복사한다."
|
||||
- "LLM_변경여부는 항상 NO여야 하며, 다른 값이면 보고서 INVALID."
|
||||
- "blocked_actions는 산문으로 완화하지 않는다."
|
||||
|
||||
backdata_feature_bank_table:
|
||||
purpose: "GAS 자동 수집 진입-청산 백데이터 원장. 수동 등록보다 GAS 원장을 우선한다."
|
||||
canonical_ref: "spec/16_data_gaps_roadmap.yaml:phase_4_backdata_collection.B1_gas_backdata_feature_bank"
|
||||
columns: ["Record_Date", "Trade_ID", "Signal_Date", "Ticker", "Name", "Account", "Entry_Stage", "Source_Origin", "Entry_Price", "Close_At_Entry", "MA20_At_Entry", "MA60_At_Entry", "ATR20_At_Entry", "Volume_Ratio_5D", "Flow_Credit", "Late_Chase_Risk_Score", "Follow_Through_Score", "Breakout_Score", "Rebound_Preservation_Score", "Setup_Decision", "Exit_Reason", "PnL_Pct", "Holding_Days", "MAE_Pct", "MFE_Pct"]
|
||||
fill_rule:
|
||||
- "backdata_feature_bank_json 값을 그대로 사용한다."
|
||||
- "Source_Origin=GAS_AUTO 행을 우선 표기하고, performance fallback이나 manual correction은 뒤로 둔다."
|
||||
- "값이 없으면 추정하지 말고 '_' 또는 '-'로 비운다."
|
||||
|
||||
benchmark_relative_harness_table:
|
||||
purpose: "KOSPI 대비 시계열 상대평가와 위성 품질·손익·현금창출 목적 잠금을 표시한다."
|
||||
canonical_ref: "spec/13_formula_registry.yaml:BENCHMARK_RELATIVE_TIMESERIES_V1, SATELLITE_ALPHA_QUALITY_GATE_V1, SATELLITE_AGGREGATE_PNL_GATE_V1, CASH_CREATION_PURPOSE_LOCK_V1"
|
||||
columns: ["종목", "brt_verdict", "excess_drawdown_pctp", "recovery_ratio_20d", "downside_beta", "rs_line_20d_slope", "saqg_v1", "sell_reason_validity", "sapg_status", "공식_ID"]
|
||||
fill_rule:
|
||||
- "benchmark_relative_timeseries_json, saqg_json, sapg_json, cash_creation_purpose_lock_json 값을 그대로 사용한다."
|
||||
- "saqg_v1=EXCLUDED 또는 WATCHLIST_ONLY이면 BUY 후보·파일럿·HTS 주문표에 포함하지 않는다."
|
||||
- "sapg_status=SAPG_CRITICAL이면 위성 신규 BUY는 전면 차단한다."
|
||||
- "LLM이 초과낙폭·회복률·하락장 베타·RS선 기울기를 재계산하지 않는다."
|
||||
|
||||
alpha_lead_table:
|
||||
purpose: "뒷북 매수를 줄이기 위한 선행 알파 후보와 추격 금지 상태를 표시한다."
|
||||
canonical_ref: "spec/13b_harness_formulas.yaml:ALPHA_LEAD_SCORE_V1, FOLLOW_THROUGH_CONFIRM_V1"
|
||||
columns: ["종목", "alpha_lead_score", "follow_through_status", "rs_rank", "volume_surge", "upside_pct", "buy_permission", "공식_ID", "검산상태"]
|
||||
fill_rule:
|
||||
- "alpha_lead_json 및 follow_through_json 값을 그대로 사용한다."
|
||||
- "buy_permission_json이 BLOCKED이면 HTS BUY 주문표를 생성하지 않는다."
|
||||
- "선행 점수의 산문 재해석으로 final_action을 변경하지 않는다."
|
||||
|
||||
anti_distribution_table:
|
||||
purpose: "설거지·분산매도 구간의 추격 매수와 늦은 물타기를 차단한다."
|
||||
canonical_ref: "spec/13b_harness_formulas.yaml:DISTRIBUTION_RISK_SCORE_V1"
|
||||
columns: ["종목", "distribution_risk_score", "risk_label", "price_position", "val_surge_pct", "inst20d", "frg20d", "blocked_action", "공식_ID"]
|
||||
fill_rule:
|
||||
- "distribution_risk_json 값을 그대로 사용한다."
|
||||
- "risk_label=HIGH 또는 blocked_action 존재 시 BUY/ADD_ON은 BLOCKED로만 출력한다."
|
||||
|
||||
profit_preservation_table:
|
||||
purpose: "수익 포지션의 이익 보호 상태와 손절 상향·익절·트레일링 상태를 고정한다."
|
||||
canonical_ref: "spec/13b_harness_formulas.yaml:PROFIT_PRESERVATION_STATE_V1"
|
||||
columns: ["종목", "profit_state", "unrealized_pnl_pct", "ratchet_state", "tp_state", "trailing_state", "allowed_action", "공식_ID"]
|
||||
fill_rule:
|
||||
- "profit_preservation_json 값을 그대로 사용한다."
|
||||
- "수익 포지션을 현금확보 대상으로 쓸 때도 smart_cash_raise_table의 제안그룹과 충돌 여부를 표시한다."
|
||||
|
||||
smart_cash_raise_table:
|
||||
purpose: "현금확보 매도를 일괄 투매가 아니라 즉시매도·반등대기·보류로 나누는 제안 계획표."
|
||||
canonical_ref: "spec/13b_harness_formulas.yaml:SMART_CASH_RAISE_PLAN_V1, SELL_QUANTITY_ALLOCATOR_V1, REBOUND_SELL_TRIGGER_V1"
|
||||
columns: ["우선순위", "종목", "cash_raise_group", "target_cash_krw", "immediate_sell_qty", "rebound_wait_qty", "protected_qty", "rebound_trigger", "예상순현금", "공식_ID"]
|
||||
fill_rule:
|
||||
- "cash_raise_plan_json과 smart_sell_quantities_json 값을 그대로 사용한다."
|
||||
- "보유수량 미확인 또는 CAPTURE_REQUIRED이면 수량 칸에 숫자를 쓰지 않는다."
|
||||
- "rebound_wait_qty는 즉시 HTS 매도수량이 아니므로 concise_hts_input_sheet에 합산하지 않는다."
|
||||
|
||||
execution_quality_table:
|
||||
purpose: "주문 단가·호가·체결 품질·유동성 조건을 최종 주문표 전에 검산한다."
|
||||
canonical_ref: "spec/13b_harness_formulas.yaml:EXECUTION_QUALITY_GUARD_V1, LIMIT_PRICE_POLICY_V1"
|
||||
columns: ["종목", "order_type", "limit_price", "tick_status", "liquidity_status", "gap_status", "execution_permission", "차단사유", "공식_ID"]
|
||||
fill_rule:
|
||||
- "execution_quality_json과 limit_price_policy_json 값을 그대로 사용한다."
|
||||
- "tick_status != VALID 또는 execution_permission != PASS이면 HTS 주문표 검산상태 PASS 금지."
|
||||
|
||||
proposal_reference_sheet:
|
||||
purpose: "사용자 판단용 제안 원장. 가격·수량·기준시점·차단사유만 남기고 주문 실행 의미는 섞지 않는다."
|
||||
columns: ["계좌", "종목", "종목명", "제안구분", "예상지정가(원)", "기준시점(종가/장중)", "제안가 기준", "예상수량(주)", "수량 기준", "손절1(원)", "손절수량1(주)", "손절2(원)", "손절수량2(주)", "손절3(원)", "손절수량3(주)", "익절가1(원)", "익절수량1(주)", "익절가2(원)", "익절수량2(주)", "익절가3(원)", "익절수량3(주)", "실행가능여부", "차단사유"]
|
||||
fill_rule:
|
||||
- "prices_json, sell_quantities_json, buy_qty_inputs_json, decisions_json 값을 우선 사용한다."
|
||||
- "validation_status=PASS 여부와 무관하게 산출 가능한 가격·수량은 표시한다."
|
||||
- "실행가능여부는 proposal_only / execution_wait / execution_ready 중 하나를 사용한다."
|
||||
- "이 표는 HTS 즉시 입력표가 아니므로 concise_hts_input_sheet와 반드시 분리한다."
|
||||
- "SELL/TRIM/방어 제안은 예상지정가에 stop_price를 우선 사용한다."
|
||||
- "익절 제안은 예상지정가에 tp1_price를 우선 사용하고 없으면 tp2_price를 사용한다."
|
||||
- "BUY 제안은 order_blueprint limit_price 우선, 없으면 buy_qty_inputs_json 보조 입력을 사용한다."
|
||||
- "WATCH/HOLD는 주문가가 아니라 참고 방어가임을 차단사유 또는 설명에 명시한다."
|
||||
- "SELL/TRIM 제안 수량은 sell_quantities_json.sell_qty 우선 사용한다."
|
||||
- "BUY 제안 수량은 buy_qty_inputs_json.final_qty 우선 사용한다."
|
||||
- "WATCH/HOLD는 주문 실행 수량이 아니라 판단 참고 수량임을 수량 기준 컬럼에 명시한다."
|
||||
- "익절 1/2/3 가격·수량은 tp_quantity_ladder_json과 prices_json을 결합해 표시한다."
|
||||
- "손절1/2는 stop_loss.core/satellite quantity_rule 기준으로 core=50/50, satellite=70/30 분할을 표시한다."
|
||||
- "손절3은 profit_preservation_json.protected_stop_price 또는 auto_trailing_stop가 있을 때만 표시한다."
|
||||
|
||||
reference_price_ledger:
|
||||
purpose: "WATCH/PENDING/BLOCKED 행의 참고 가격 원장. 주문표가 아니며 평가용 참고 상태를 공개한다."
|
||||
title_required: "WATCH 감시 원장 — 주문 아님, HTS 입력 금지"
|
||||
columns: ["ticker", "name", "reference_stop_price", "reference_price_basis", "reference_tp_state", "hts_allowed", "reason_code"]
|
||||
forbidden_columns: ["지정가", "손절가", "익절가", "매도가", "주문가", "주문수량", "손절수량", "익절수량", "매도수량", "주문금액"]
|
||||
fill_rule:
|
||||
- "hts_allowed는 항상 false로 기재한다."
|
||||
- "WATCH 행에 주문 가능 단가·수량처럼 보이는 한글 컬럼명을 쓰면 INVALID_COLUMN 처리한다."
|
||||
- "reference_tp_state는 tp1/tp2 상태를 함께 공개한다. 예: tp1=PENDING; tp2=PENDING."
|
||||
|
||||
capture_read_ledger_report:
|
||||
purpose: "계좌·보유·현금 판독 증빙 원장. 이 표 없이 HTS 주문표를 출력하지 않는다."
|
||||
columns: ["파일/화면", "계좌", "화면종류", "읽은값", "확신도", "주문표반영", "판독_상태", "다음확인사항"]
|
||||
fill_rule:
|
||||
- "spec/00_execution_contract.yaml:capture_read_ledger.columns를 우선 적용한다."
|
||||
- "보유수량·평단·현금·미체결 주문의 출처를 읽은값에 구분해 적는다."
|
||||
- "NOT_PROVIDED 또는 CAPTURE_READ_FAILED이면 해당 계좌 주문수량은 산출하지 않는다."
|
||||
prohibition:
|
||||
- "자동투자/약정 화면 수치를 보유수량·평단·현금으로 사용 금지"
|
||||
- "원장 행 없이 본문에서 총자산·현금·보유수량 근거 생성 금지"
|
||||
|
||||
order_quantity_4stage_gate_report:
|
||||
purpose: "주문표 출력 직전 4단계 검산 결과 표."
|
||||
columns: ["계좌", "종목명", "1단계_원장", "2단계_현금", "3단계_보유수량", "4단계_미체결", "최종검산상태", "차단사유", "다음확인사항"]
|
||||
pass_rule: "네 단계가 모두 통과일 때만 immediate_execution_playbook 검산상태=PASS를 허용한다."
|
||||
fail_rule: "하나라도 실패·미확인이면 주문표 대신 산출금지 사유를 출력한다."
|
||||
|
||||
decision_trace_table:
|
||||
purpose: "상태 머신의 각 판단 단계에서 어떤 규칙이 적용되어 어떤 결론이 선택/차단됐는지 남기는 재현성 검산 표."
|
||||
canonical_ref: "spec/09_decision_flow.yaml:decision_flow.deterministic_execution_control"
|
||||
activation:
|
||||
trigger: "모든 분석·주문·리뷰 출력에서 필수"
|
||||
priority: "order_quantity_4stage_gate 이후, 보유주 진단 및 HTS 주문표 이전"
|
||||
columns: ["단계", "check_id", "rule_ref", "사용입력", "결과", "선택행동", "차단행동", "누락입력", "동률처리", "다음상태"]
|
||||
fill_rule:
|
||||
- "각 행은 decision_flow.states의 상태 또는 output validation 단계 하나를 나타낸다."
|
||||
- "rule_ref는 파일 경로와 YAML path를 함께 적는다."
|
||||
- "사용입력은 실제 사용한 필드명만 적고, 값이 민감하거나 길면 evidence 참조로 대체한다."
|
||||
- "누락입력이 있으면 선택행동은 BLOCKED/INSUFFICIENT_DATA/WATCH 중 하나로 제한한다."
|
||||
- "동률처리는 deterministic_execution_control.tie_breaker_order 번호를 적는다."
|
||||
prohibition:
|
||||
- "판단 추적표 없이 final_action 또는 HTS 주문표 출력 금지"
|
||||
- "decision_trace에 없는 rule_id를 근거로 최종 결론 변경 금지"
|
||||
- "동률처리 없이 동일 점수 후보 중 임의 선택 금지"
|
||||
|
||||
sell_priority_decision_table:
|
||||
purpose: "여러 매도·축소·교체 후보가 동시에 존재할 때 HTS 주문 실행 순서를 정하는 포트폴리오 단위 표. 활성 시 current_holdings_analysis_report_template보다 먼저 렌더링한다."
|
||||
canonical_ref: "spec/risk/portfolio_exposure.yaml:portfolio_exposure_framework.sell_priority_engine"
|
||||
activation:
|
||||
trigger:
|
||||
- "SELL/TRIM/ROTATE 후보가 2개 이상"
|
||||
- "cash_floor 미달 또는 이벤트 주 현금 상향 필요"
|
||||
- "중복노출 축소와 보유주 진단 축소 후보가 동시에 존재"
|
||||
priority: "order_quantity_4stage_gate 이후, current_holdings_analysis_report_template보다 먼저"
|
||||
columns: ["우선순위", "계좌", "종목명", "실행그룹", "현재보유수량", "매도가능검산", "매도유형", "우선순위단계", "Sell_Priority_Score", "예상순현금", "세금/비용", "매도사유", "보류사유", "다음확인사항"]
|
||||
fill_rule:
|
||||
- "우선순위단계는 sell_priority_engine.hard_precedence 중 하나를 사용한다."
|
||||
- "Sell_Priority_Score는 동일 단계 후보 정렬용이며 hard_precedence를 뒤집지 않는다."
|
||||
- "현재보유수량 또는 평균단가가 미확인이면 매도가능검산=미통과, 수량은 미산출로 둔다."
|
||||
- "예상순현금은 세금·수수료 미확인 시 숫자 대신 '세금/비용 확인 필요'로 둔다."
|
||||
prohibition:
|
||||
- "이 표 없이 여러 매도 후보의 실행 순서를 산문으로만 제시 금지"
|
||||
- "보유수량 미확인 후보에 정수 매도수량 기재 금지"
|
||||
- "세금 최적화만으로 hard stop 후보를 후순위로 미루지 않는다."
|
||||
|
||||
market_context_learning_note:
|
||||
purpose: "거시·미시 경제 상황, 시장 움직임, 투자자 고민 포인트, 핵심 용어, 판단근거를 학습용으로 정리하는 조건부 해설 블록."
|
||||
activation:
|
||||
trigger:
|
||||
- "사용자가 학습형 설명을 요청한 경우"
|
||||
- "주간·월간 점검 보고서"
|
||||
- "시장국면 변경, 리밸런싱, 대규모 매도/축소, 신규 주도주 편입 검토가 발생한 경우"
|
||||
priority: "주문 검산·HTS 주문표·보유주 진단 이후, 부록 또는 후반부"
|
||||
note: "실행 판단을 먼저 확정한 뒤 독자의 이해를 돕기 위한 해설로만 사용한다."
|
||||
columns: ["구분", "현재 관찰값/상태", "투자자가 고민할 점", "판단에 미친 영향", "핵심 용어", "다음 확인 데이터"]
|
||||
row_types:
|
||||
macro: "금리, 환율, 유가, VIX, KOSPI/KOSDAQ 추세, credit spread 등 포트폴리오 위험예산과 현금비중에 영향을 주는 배경."
|
||||
micro_market: "섹터 순환, 주도주 집중, 거래대금, 외국인/기관 수급, ETF·직접주 중복노출 등 시장 내부 움직임."
|
||||
investor_dilemma: "지금 사야 하는가, 기다려야 하는가, 현금을 얼마나 남겨야 하는가, 손실 종목을 버틸 근거가 있는가 같은 실제 의사결정 질문."
|
||||
decision_basis: "BUY/HOLD/SELL/TRIM/ROTATE/WATCH 결론에 영향을 준 rule_id, 수치, hard stop, missing_data."
|
||||
glossary: "Total_Heat, cash_floor, ATR20, Expected_Edge, Flow_Rows, flow_credit, lead/lag/hybrid mode 등 보고서 내 핵심 용어."
|
||||
fill_rule:
|
||||
- "현재 관찰값/상태는 data_basis, data_completeness_matrix, risk_gate, market_regime_state, sector_flow, macro에서 검증된 값만 사용한다."
|
||||
- "판단에 미친 영향은 어떤 주문 결론을 새로 만들지 말고 이미 확정된 portfolio_decision 또는 prohibited_calculations에 연결한다."
|
||||
- "다음 확인 데이터는 구체 출처를 적는다. 예: macro 시트, sector_flow 시트, HTS 보유종목 화면, 미체결 주문 화면."
|
||||
- "수치가 없으면 추정하지 말고 '미확인' 또는 '다음 확인 필요'로 적는다."
|
||||
example_rows:
|
||||
macro: ["거시", "VIX·환율·KOSPI MA20 상태 확인 필요", "현금비중을 낮춰도 되는가", "MRS와 cash_floor 판단에만 반영", "MRS, cash_floor", "macro 시트, KOSPI MA20, USD/KRW"]
|
||||
micro_market: ["미시/시장", "특정 섹터 수급 집중 여부 확인", "ETF와 직접주 중 어느 노출이 효율적인가", "중복노출·리밸런싱 후보 판단에 반영", "sector_flow, duplicate_exposure", "sector_flow, core_satellite"]
|
||||
investor_dilemma: ["고민 포인트", "매수 신호는 있어도 ATR20 또는 현금이 미확인일 수 있음", "기회비용과 검산 실패 중 무엇을 우선할 것인가", "validation_status 미통과 시 주문 금지", "ATR20, validation_status", "data_feed, account_snapshot"]
|
||||
decision_basis: ["판단근거", "Total_Heat·cash_floor·Flow_Rows가 핵심 gate", "왜 A등급이어도 즉시매수가 아닐 수 있는가", "hard stop이 전략 점수보다 우선", "Total_Heat, Flow_Rows", "risk_gate, triggered_rules"]
|
||||
glossary: ["용어", "Expected_Edge는 기대수익 대비 손실위험 비율", "손절폭이 넓으면 수량이 줄어드는 이유", "position_sizing 산식 이해 보조", "Expected_Edge, ATR20", "spec/13_formula_registry.yaml"]
|
||||
output_rule:
|
||||
- "각 행은 교육 목적의 해설이며 주문 산출 근거 표를 대체하지 않는다."
|
||||
- "항목별 설명은 2~3문장 이내로 제한한다."
|
||||
- "용어 설명은 보고서에서 실제 사용한 용어를 우선한다."
|
||||
- "판단근거는 rule_id 또는 파일 경로를 함께 적는다."
|
||||
prohibition:
|
||||
- "학습 해설에서 신규 매수·매도 결론 생성 금지"
|
||||
- "학습 해설만으로 validation_status를 PASS로 변경 금지"
|
||||
- "웹이나 일반 지식만으로 workbook/account_snapshot 결론 대체 금지"
|
||||
- "출처 없는 최신 시장 상황 단정 금지"
|
||||
|
||||
data_flow_analysis_report:
|
||||
purpose: >
|
||||
블록 2(데이터 완성도 매트릭스) 직후, 블록 3(캡처 판독 원장) 전에 1페이지로 삽입.
|
||||
데이터→신호→판단→행동 4단계 흐름을 표로 가시화해 병목 단계를 즉시 파악.
|
||||
flow_table:
|
||||
columns:
|
||||
- "종목명"
|
||||
- "[D]데이터상태 (OK/PARTIAL/MISSING)"
|
||||
- "[S]신호 (추세/수급/유동성 각각 Pass/Fail)"
|
||||
- "[J]판단 (A/B/C/D등급·이유)"
|
||||
- "[A]행동 (즉시/조건부/보류/금지)"
|
||||
- "병목단계 (D/S/J 중 어느 단계가 블록인지)"
|
||||
- "다음확인우선순위"
|
||||
fill_rule:
|
||||
- "[D]: 블록 2 데이터 완성도 매트릭스에서 직접 참조."
|
||||
- "[S]: minimalist_buy_gate 3핵심 지표 결과."
|
||||
- "[J]: sector_model.grade + Expected_Edge + flow_credit 종합. (결합 공식 → fill_rule_J_detail)"
|
||||
- "[A]: 최종 즉시실행/조건부/보류/금지 결론."
|
||||
- "병목단계: [D]에서 막히면 'D', [S]에서 막히면 'S', [J]에서 막히면 'J'."
|
||||
absence_rule: "통과 이유 없는 A등급 금지. 모든 칸은 값 또는 '없음'으로 채운다."
|
||||
fill_rule_J_detail: # [P139] [J] 단계별 상한 설정 — 3개 시스템 결합 공식
|
||||
step_1: "sector_model.score_axes_formula 산출 → A_core/B_wait/C_watch/D_exclude 상한 설정"
|
||||
step_2: "Expected_Edge < 1.5이면 최대 B등급 (A 승격 불가)"
|
||||
step_3: "flow_credit < 0.40이면 최대 C등급 (A·B 승격 불가)"
|
||||
step_4: "step_1~3 모두 통과 시에만 sector_model.grade 결론 채택"
|
||||
precedence: "Expected_Edge 기준 > sector_model.grade (충분조건 vs 필요조건)"
|
||||
output_format: "A/B/C/D 중 하나 + 근거 (예: B — Expected_Edge 1.8 충족, flow_credit 0.55 partial)"
|
||||
minimalist_output:
|
||||
- "3지표([S]) 모두 Pass인 종목은 강조 표시."
|
||||
- "병목이 [D]인 종목은 '다음확인우선순위'에 구체 출처 명시."
|
||||
- "병목이 [S]인 종목은 실패한 지표와 임계치 명시."
|
||||
|
||||
current_holdings_analysis_report_template:
|
||||
purpose: "현재 보유주를 대상으로 유지/축소/교체/관찰을 판단하는 전용 보고서 양식."
|
||||
activation:
|
||||
trigger: "보유수량이 확인된 종목이 1개 이상이거나 position_review_cycle.documentation 점검 주기에 도달했을 때"
|
||||
priority: "제안 검토 표 다음, 매수/매도 예시 이전"
|
||||
note: "신규 종목 추천이 아니라 기존 보유 포지션 점검 전용이다."
|
||||
decision_score_formula: >
|
||||
current_holdings_score = 0.30*trend_score + 0.30*flow_score + 0.20*edge_score + 0.10*concentration_score + 0.10*execution_score.
|
||||
trend_score, flow_score, edge_score, concentration_score, execution_score는 모두 0~100 정규화 값으로 입력한다.
|
||||
score_components: # [R4] 컴포넌트별 산출 규칙
|
||||
trend_score:
|
||||
"100": "현재가 > 20일선 AND 20일선 > 60일선 AND 5일선 위 주행"
|
||||
"70": "현재가 > 20일선 AND 20일선 > 60일선 (5일선 이탈)"
|
||||
"40": "현재가 > 20일선 (60일선 하향배열)"
|
||||
"10": "현재가 < 20일선"
|
||||
missing: "20일선·60일선 미확인 → 50점(중립)"
|
||||
flow_score:
|
||||
"100": "Frg_5D + Inst_5D 동반 순매수 AND flow_credit >= 0.60"
|
||||
"70": "Frg_5D 또는 Inst_5D 중 1개 순매수 AND flow_credit >= 0.40"
|
||||
"40": "flow_credit 0.20~0.39"
|
||||
"10": "Frg_5D + Inst_5D 동반 순매도"
|
||||
missing: "Flow_OK=N 또는 Flow_Rows<5 → 25점"
|
||||
edge_score:
|
||||
formula: "Expected_Edge = (익절가 - 진입가) / (진입가 - 손절가)"
|
||||
"100": "Expected_Edge >= 2.5"
|
||||
"70": "1.8 <= Expected_Edge < 2.5"
|
||||
"40": "1.2 <= Expected_Edge < 1.8"
|
||||
"10": "Expected_Edge < 1.2"
|
||||
missing: "손절가·익절가 미설정(legacy_position) → 0점"
|
||||
concentration_score:
|
||||
"100": "current_weight <= target_weight"
|
||||
"70": "target_weight < current_weight <= target_weight + 3%p"
|
||||
"40": "target_weight + 3%p < current_weight <= target_weight + 5%p"
|
||||
"10": "current_weight > target_weight + 5%p"
|
||||
missing: "target_weight 미설정 → 50점(중립)"
|
||||
execution_score:
|
||||
"100": "진입 후 D+5 이내 수익 구간 진입 OR 시범진입 후 본진입 계획 유효"
|
||||
"70": "진입 후 D+20 이내 플러스 구간 유지"
|
||||
"40": "진입 후 D+20 경과, 0% 근방"
|
||||
"10": "진입 후 D+20 경과, 손실 구간 AND time_stop 임박"
|
||||
missing: "진입일 미확인 → 50점(중립)"
|
||||
thresholds:
|
||||
keep: "score >= 70 AND current_weight within target_band ±5%p AND grade in (A,B)"
|
||||
trim: "score 50~69 OR current_weight exceeds target_band by >5%p OR duplicate_factor_overweight"
|
||||
rotate: "score < 50 OR grade in (C,D) AND flow_negative_20d"
|
||||
watch: "보유수량 0 또는 판독 미완료"
|
||||
keep_criteria:
|
||||
- "현재비중이 목표비중 대비 ±5%p 이내"
|
||||
- "등급 A 또는 B"
|
||||
- "기대수익비(Expected_Edge) >= 1.8"
|
||||
- "20일 수급과 추세가 동시에 유지"
|
||||
trim_criteria:
|
||||
- "현재비중이 목표비중 대비 +5%p 초과"
|
||||
- "기대수익비(Expected_Edge) 1.2~1.8"
|
||||
- "동일 섹터 또는 동일 팩터 중복 노출이 상한 초과"
|
||||
- "수익률이 플러스여도 포트폴리오 집중도만 과다하면 축소"
|
||||
rotate_criteria:
|
||||
- "등급 C 또는 D"
|
||||
- "20일 수급 음수"
|
||||
- "반등 실패 또는 대체주 우위"
|
||||
- "손익률 -5% 이하이면서 회복 논리 부재"
|
||||
watch_criteria:
|
||||
- "보유수량 0"
|
||||
- "평단 또는 현재가 판독 미완료"
|
||||
- "핵심 데이터 누락으로 score 산출 불가"
|
||||
columns: ["계좌", "종목명", "현재보유수량", "평단", "현재가", "현재비중(%)", "손익률(%)", "섹터/팩터", "등급", "판단점수", "유지/축소/교체", "근거", "다음점검일"]
|
||||
input_example:
|
||||
fields: ["trend_score", "flow_score", "edge_score", "concentration_score", "execution_score", "current_weight", "target_band", "grade", "Expected_Edge"]
|
||||
sample_values: ["85", "78", "82", "60", "75", "12.4", "10.0", "A", "2.1"]
|
||||
sample_calc: "0.30*85 + 0.30*78 + 0.20*82 + 0.10*60 + 0.10*75 = 78.2"
|
||||
sample_judgement: "점수 78.2, 현재비중 12.4%, 목표밴드 10.0% 대비 +2.4%p, grade A이므로 유지"
|
||||
sample_fill_rule: "trend/flow/edge는 데이터에서, concentration/execution은 계좌비중과 주문검산에서 채운다."
|
||||
output_phrases:
|
||||
keep: "유지 — 점수 {score}, 비중 {current_weight}%가 목표밴드 {target_band}% 이내, 등급 {grade}, 기대수익비 {expected_edge}."
|
||||
trim: "축소 — 점수 {score}, 현재비중이 목표밴드 대비 과다, 중복 노출 또는 기대수익비 저하 확인."
|
||||
rotate: "교체 — 점수 {score}, 20일 수급 음수 또는 반등 실패로 기존 포지션 교체 필요."
|
||||
watch: "관찰 — 보유수량 0 또는 핵심 데이터 미완료로 판단 보류."
|
||||
output_rule:
|
||||
- "판단점수와 등급을 함께 표기한다."
|
||||
- "유지/축소/교체/관찰 중 하나만 최종 출력한다."
|
||||
- "문장형 근거와 수치형 점수를 동시에 보여준다."
|
||||
keep_row: ["일반계좌", "예시종목", "120", "24800", "27500", "12.4", "+10.8", "반도체", "A", "82", "유지", "주도 섹터 + 수급 유지 + 기대수익비 충족", "D+5"]
|
||||
trim_row: ["ISA", "예시종목", "80", "38000", "36000", "8.1", "-5.3", "전력기기", "B", "61", "축소", "섹터 중복 + 기대수익비 저하 + 리밸런싱 대상", "D+3"]
|
||||
rotate_row: ["연금저축", "예시종목", "50", "59000", "54500", "4.2", "-7.6", "개별 성장주", "C", "42", "교체", "수급 이탈 + 반등 실패 + 대체주 우위", "D+2"]
|
||||
watch_row: ["일반계좌", "예시종목", "0", "0", "0", "0.0", "0.0", "없음", "D", "0", "관찰", "보유 없음 또는 판독 미완료", "D+7"]
|
||||
required_inputs:
|
||||
- "현재보유수량"
|
||||
- "평단"
|
||||
- "현재가"
|
||||
- "현재비중"
|
||||
- "섹터/팩터"
|
||||
- "등급"
|
||||
- "기대수익비"
|
||||
prohibition:
|
||||
- "현재보유수량·평단·현재가 없이 보유주 분석 금지"
|
||||
- "신규매수 제안과 동일 표에 혼용 금지"
|
||||
- "보유주 분석에서 손절가·익절가를 강제로 생성하지 않는다"
|
||||
|
||||
market_leader_screening_report_template:
|
||||
purpose: "떠오르는 주도주를 관찰 후보·편입 후보·금지 후보로 나누는 전용 스크리닝 보고서."
|
||||
activation:
|
||||
trigger: "sector_flow 또는 quant_feed 갱신 시, 또는 매주 정기 점검 시"
|
||||
priority: "current_holdings_analysis_report_template 및 immediate_execution_playbook 다음"
|
||||
note: "이 블록은 core_satellite 자산배분 표와 분리된 주도주 발굴용이다."
|
||||
source_binding:
|
||||
required_sheets: ["core_satellite", "sector_flow", "macro", "event_risk"]
|
||||
freshness_rule: "주도주 후보 표의 모든 행은 Price_Date, sector_flow.AsOfDate, macro.AsOfDate를 함께 적고, 하나라도 다르면 '데이터 불일치'로 표시한다."
|
||||
unit_normalization_rule: "AvgTradeValue_5D_M와 AvgTradeValue_20D_M는 보고서 출력 시 억원 단위로 환산해 표기한다. 원 단위와 혼용 금지."
|
||||
quantity_integrity_rule: "보유수량, 주문수량, 계좌원장, 판독상태 중 하나라도 미확정이면 후보 분류보다 '판독 미완료'를 우선한다."
|
||||
columns: ["종목명", "섹터", "Rotation_Score", "5D수급", "20D수급", "상대강도", "유동성", "실적추정", "등급", "분류", "액션", "편입근거", "금지근거"]
|
||||
observe_row: ["한미반도체", "반도체 장비", "88", "양호", "양호", "우수", "충분", "상향", "A", "관찰 후보", "현금·중복노출 확인 후 재평가", "반도체 과다보유 시 즉시 편입 금지"]
|
||||
include_row: ["기아", "자동차", "82", "양호", "양호", "우수", "충분", "유지", "A", "편입 후보", "섹터 순환매와 수급 동시 확인", "현금 미확보 시 추격매수 금지"]
|
||||
ban_row: ["LS ELECTRIC", "AI전력", "58", "혼조", "혼조", "보통", "충분", "혼조", "C", "금지 후보", "테마는 강하나 단기 수급 이탈", "상대강도 회복 전까지 신규매수 금지"]
|
||||
incomplete_row: ["TIGER 코리아AI전력기기", "AI전력 ETF", "판독 미완료", "판독 미완료", "판독 미완료", "판독 미완료", "판독 미완료", "판독 미완료", "판독 미완료", "판독 미완료", "수량·비중·판독상태 미확정", "수량 미확정은 후보 판단보다 먼저 보류"]
|
||||
candidate_gate:
|
||||
observe_candidate: "Allowed_Action=CONDITIONAL_HOLD 이고 Rotation_Score >= 50 이면 관찰 후보"
|
||||
include_candidate: "Allowed_Action=CONDITIONAL_HOLD 이고 sector_flow.Alert_Level in (INFLOW_MODERATE, NEUTRAL) 이며 중복노출이 과도하지 않을 때 편입 후보"
|
||||
ban_candidate: "Allowed_Action=SELL_ALLOWED 또는 sector_flow.Alert_Level in (OUTFLOW_ALERT, OUTFLOW_CAUTION) 이면 금지 후보"
|
||||
incomplete_candidate: "판독 미완료 또는 수량 미확정이면 어떤 등급도 부여하지 않고 데이터 재확인으로 되돌린다."
|
||||
rules:
|
||||
- "주도주 후보는 core_satellite 자산배분 후보와 다른 개념으로 표기한다."
|
||||
- "관찰 후보는 강하지만 계좌 중복노출이 높아 즉시 매수하면 안 되는 종목이다."
|
||||
- "편입 후보는 현금·수량·손절가·익절가를 갖춘 경우에만 매수 제안으로 전환한다."
|
||||
- "금지 후보는 테마성 강세라도 수급·유동성·실적 중 하나라도 훼손되면 신규매수 금지로 둔다."
|
||||
- "핵심 데이터의 단위가 맞지 않으면 후보 분류보다 데이터 불일치 표기를 우선한다."
|
||||
- "수량 판독이 불완전한 종목은 절대 관찰/편입 후보로 승격하지 않는다."
|
||||
required_inputs:
|
||||
- "sector_flow Rotation_Score"
|
||||
- "5D/20D 수급"
|
||||
- "상대강도"
|
||||
- "유동성"
|
||||
- "실적추정"
|
||||
- "기존 보유와의 중복노출"
|
||||
prohibition:
|
||||
- "주도주 후보를 core_satellite 후보로 오독하지 않는다"
|
||||
- "관찰 후보를 즉시매수 후보로 승격하지 않는다"
|
||||
- "금지 후보를 테마가 강하다는 이유로 편입하지 않는다"
|
||||
|
||||
market_micro_macro_flow_scenario_report_template:
|
||||
purpose: "미시(종목)·거시(국면)·전체 자금흐름·향후 시나리오를 한 페이지로 묶는 통합 진단 보고서."
|
||||
activation:
|
||||
trigger: "macro_snapshot, sector_flow, quant_feed 중 2개 이상 갱신되었거나 주간 점검일에 도달했을 때"
|
||||
priority: "current_holdings_analysis_report_template 다음, buy/sell 예시 이전"
|
||||
note: "미시·거시·흐름 중 하나라도 빠지면 시나리오 결론을 내리지 않고 '보류'로 적는다."
|
||||
micro_panel:
|
||||
columns: ["종목명", "가격상태", "수급상태", "유동성상태", "실적상태", "등급", "판단"]
|
||||
required_inputs: ["Price_Status", "Flow_OK", "Flow_Rows", "AvgTradeValue_5D_M", "컨센서스 방향", "등급"]
|
||||
rule: "개별 종목은 추세·수급·유동성·실적 4축이 동시에 맞아야 A/B 판정을 허용한다."
|
||||
macro_panel:
|
||||
columns: ["지표", "최근값", "추세", "국면판정", "포트폴리오함의"]
|
||||
required_inputs: ["KOSPI", "KOSDAQ", "USD/KRW", "VIX", "미국 10년물", "미국 HY OAS", "WTI", "국내 CP/CD 스프레드"]
|
||||
rule: "거시는 위험선호/중립/위험회피의 국면판정에만 사용하고, 개별 종목의 가격대는 대체하지 않는다."
|
||||
capital_flow_panel:
|
||||
columns: ["흐름항목", "방향", "강도", "해석", "행동"]
|
||||
required_inputs: ["외국인 5D/20D", "기관 5D/20D", "sector_flow Rotation_Score", "ETF 상대강도", "현금비중", "미체결 주문"]
|
||||
rule: "전체 자금흐름은 매수 트리거가 아니라 비중조절·후행진입·리밸런싱 우선순위를 정하는 입력값이다."
|
||||
scenario_matrix:
|
||||
columns: ["시나리오", "발동조건", "확률가중", "예상국면", "우선행동", "금지행동"]
|
||||
base_probability_rule: "점수 대신 국면·흐름·가격 일치 정도로 가중만 부여하고, 정밀 확률처럼 과장하지 않는다."
|
||||
bull_extension:
|
||||
trigger: "Risk-On + VIX<18 + KOSPI 20일선 위 + 외국인/기관 20D 순매수 + sector_flow 집중"
|
||||
probability_weight: "높음"
|
||||
implication: "주도주 유지, 선행형은 소액 시범진입, 약한 보유주는 축소"
|
||||
action: "코어 유지, 위성은 주도 섹터로만 제한적으로 이동"
|
||||
rotation:
|
||||
trigger: "KOSPI 횡보 + 섹터별 상대강도 급변 + 자금이 기존 주도주에서 신규 리더로 이동"
|
||||
probability_weight: "중간"
|
||||
implication: "보유주 교체보다 자금 재배치 우선"
|
||||
action: "보유주 진단에서 교체 후보와 유지 후보 분리"
|
||||
risk_off_squeeze:
|
||||
trigger: "VIX>25 또는 KOSPI 20일·60일선 동시 하회 + 외국인/기관 20D 동반 매도"
|
||||
probability_weight: "중간~높음"
|
||||
implication: "현금 비중 확대, 신규매수 축소, 반등매도 우선"
|
||||
action: "후행형만 허용, 전술 위성 축소, 리밸런싱 우선"
|
||||
event_shock:
|
||||
trigger: "DART 리스크/금리/환율/유가 급변으로 1~3일 내 가격·수급 동시 충격"
|
||||
probability_weight: "낮음~중간"
|
||||
implication: "일시적 노이즈와 구조적 훼손을 분리"
|
||||
action: "즉시 추격매수 금지, 데이터 재확인 후 대응"
|
||||
sideways_drift:
|
||||
trigger: "가격·수급·거시가 모두 중립이고 거래대금이 축소"
|
||||
probability_weight: "중간"
|
||||
implication: "회전율 낮추고 현금·대기 비중을 높임"
|
||||
action: "보유주 유지/관찰 중심, 신규진입 보류"
|
||||
output_rule:
|
||||
- "미시·거시·자금흐름·시나리오를 한 표 안에 함께 보여준다."
|
||||
- "시나리오 결론은 우선행동과 금지행동을 동시에 써야 한다."
|
||||
- "확률은 정밀 예측이 아니라 상대적 가중으로만 표기한다."
|
||||
prohibition:
|
||||
- "거시만 보고 종목 매수/매도 결론을 내리지 않는다"
|
||||
- "자금흐름만 보고 추격매수 결론을 내리지 않는다"
|
||||
- "시나리오를 한 가지만 고정해 다른 국면 전환 가능성을 지우지 않는다"
|
||||
|
||||
engine_feedback_loop_report:
|
||||
purpose: "매일 제안값과 다음 거래일 결과를 비교해 오차 원인과 개선 제안을 누적하는 평가 섹션."
|
||||
canonical_ref: "Temp/proposal_evaluation_history.json"
|
||||
location: "QEH_AUDIT_BLOCK 직후"
|
||||
columns: ["제안일", "평가일", "종목", "제안", "검산", "제안가", "결과가", "T+1수익률%", "평가", "원인", "개선제안"]
|
||||
fill_rule:
|
||||
- "tools/update_proposal_evaluation_history.py가 생성한 proposal_evaluation_history records를 사용한다."
|
||||
- "T+1 평가 전 행은 PENDING_T1로 두고 성패를 단정하지 않는다."
|
||||
- "MISMATCHED 행은 원인을 rule/feature 수준으로 분류하고 개선 제안을 남긴다."
|
||||
- "표본이 적을 때는 임계치 변경을 제안하되 즉시 강제하지 않는다."
|
||||
|
||||
prediction_evaluation_improvement_report:
|
||||
purpose: "예측 결과 평가 수치와 YAML/GS/JSON/PY 갭을 함께 보여주어 엔진 개선 우선순위를 정하는 섹션."
|
||||
canonical_ref: "Temp/proposal_evaluation_history.json, tools/measure_harness_coverage.py, tools/update_proposal_evaluation_history.py"
|
||||
location: "engine_feedback_loop_report 직후, alpha_feedback_loop_report 이전"
|
||||
columns: ["항목", "값", "의미", "갭"]
|
||||
fill_rule:
|
||||
- "proposal_evaluation_history summary와 하네스 커버리지 측정 결과를 그대로 사용한다."
|
||||
- "YAML/GS/JSON/PY 각 층의 커버리지와 갭을 숫자로 병기한다."
|
||||
- "개선 제안은 평가 기록의 improvement_proposal과 error_cause를 우선 사용한다."
|
||||
- "이 섹션은 다음날 엔진 개선과 반복오류 방지의 근거 원장이다."
|
||||
- "gap 경고가 '경고'이면 운영 검증 실패로 처리하고 주문 실행표를 차단한다."
|
||||
- "커버리지 목표는 YAML/GS/JSON/PY 각 100%이며 미달 시 원인코드를 함께 출력한다."
|
||||
|
||||
core_satellite_timing_gate_table:
|
||||
purpose: "core_satellite 후보의 T+1 강제매도 위험과 매도충돌을 분리 평가하는 표."
|
||||
canonical_ref: "spec/13_formula_registry.yaml:T1_FORCED_SELL_RISK_V1"
|
||||
location: "reference_price_ledger 직후, alpha_feedback_loop_report 이전"
|
||||
columns: ["종목", "종목명", "후보등급", "실행추천상태", "타이밍", "진입점수", "청산점수", "T+1위험", "T+1상태", "매도충돌", "충돌상태", "공식근거"]
|
||||
fill_rule:
|
||||
- "core_satellite.Execution_Recommendation_State를 그대로 출력한다."
|
||||
- "BUY_PILOT_ALLOWED 이외 상태는 HTS 주문표로 승격하지 않는다."
|
||||
- "SELL_OR_TRIM_FIRST, BUY_BLOCKED_SELL_CONFLICT, BUY_BLOCKED_T1_EXIT_RISK 순으로 중요도 정렬한다."
|
||||
|
||||
# ── [2026-05-20_HARNESS_V5] H6: 뒷박 차단 게이트 표 ────────────────────────
|
||||
breakout_quality_gate_table:
|
||||
purpose: "신규 BUY 후보의 뒷박(Late Chase) 차단 점수와 게이트 상태를 하네스 값으로 표시."
|
||||
canonical_ref: "spec/13_formula_registry.yaml:BREAKOUT_QUALITY_GATE_V2"
|
||||
harness_key: "breakout_quality_gate_json"
|
||||
location: "execution_quality_table 직후, order_quantity_4stage_gate 이전"
|
||||
columns:
|
||||
- "종목"
|
||||
- "종목명"
|
||||
- "뒷박점수(0~100)"
|
||||
- "게이트상태"
|
||||
- "주요차단사유"
|
||||
- "BUY허용여부"
|
||||
gate_states:
|
||||
PILOT_ALLOWED: "BUY 허용 — 정상 진입 가능"
|
||||
WATCH_COOLING_OFF: "대기 — 추격 리스크 있음. 신규 BUY 보류."
|
||||
BLOCKED_LATE_CHASE: "차단 — 뒷박 판정. BUY 절대 금지."
|
||||
fill_rule:
|
||||
- "breakout_quality_gate_json 하네스 값을 그대로 복사한다. LLM 재계산 금지."
|
||||
- "BLOCKED_LATE_CHASE 종목은 BUY허용여부='NO (H6 차단)' 기재 후 주문표 승격 금지."
|
||||
- "breakout_quality_gate_json 없으면 표 전체를 'DATA_MISSING — GAS 재실행 필요'로 표시."
|
||||
prohibition:
|
||||
- "breakout_quality_score를 LLM이 재계산하거나 조정 금지"
|
||||
- "BLOCKED_LATE_CHASE 상태를 서사로 완화해 BUY 허용으로 승격 금지"
|
||||
- "이 표 없이 신규 BUY 주문표 출력 금지 (BLOCKED_BREAKOUT_GATE_MISSING)"
|
||||
version: "2026-05-20_HARNESS_V5"
|
||||
|
||||
# ── [2026-05-20_HARNESS_V5] H7: 가짜 매도 차단 게이트 표 ──────────────────
|
||||
anti_whipsaw_gate_table:
|
||||
purpose: "매도 후보의 가짜 매도(Whipsaw) 가능성을 하네스 값으로 표시하고 1거래일 홀드 여부 결정."
|
||||
canonical_ref: "spec/13b_harness_formulas.yaml:ANTI_WHIPSAW_HOLD_GATE_V1"
|
||||
harness_key: "anti_whipsaw_gate_json"
|
||||
location: "breakout_quality_gate_table 직후"
|
||||
columns:
|
||||
- "종목"
|
||||
- "종목명"
|
||||
- "Whipsaw점수"
|
||||
- "게이트상태"
|
||||
- "홀드일수"
|
||||
- "당일매도허용여부"
|
||||
- "주요사유"
|
||||
gate_states:
|
||||
CONFIRMED_SELL: "매도 허용"
|
||||
INCONCLUSIVE: "50% 매도 허용, 50% 1거래일 후 재평가"
|
||||
WHIPSAW_SUSPECTED: "당일 매도 전량 차단 — 1거래일 홀드"
|
||||
fill_rule:
|
||||
- "anti_whipsaw_gate_json 하네스 값을 그대로 복사한다. LLM 재계산 금지."
|
||||
- "WHIPSAW_SUSPECTED 종목은 당일 매도허용여부='NO (H7 WHIPSAW_SUSPECTED)' 기재."
|
||||
- "anti_whipsaw_gate_json 없으면 'DATA_MISSING — GAS 재실행 필요' 표시."
|
||||
prohibition:
|
||||
- "anti_whipsaw_score를 LLM이 재계산하거나 조정 금지"
|
||||
- "WHIPSAW_SUSPECTED 종목을 서사로 완화해 당일 전량 매도 허용 금지"
|
||||
- "이 표 없이 SELL/TRIM 주문표 출력 금지 (BLOCKED_WHIPSAW_GATE_MISSING)"
|
||||
version: "2026-05-20_HARNESS_V5"
|
||||
|
||||
# ── [2026-05-20_HARNESS_V5] H8: 4경로 현금확보 결정 표 ─────────────────────
|
||||
smart_cash_raise_v2_table:
|
||||
purpose: "현금 부족 시 4경로(ROUTE_A~D) 결정론적 현금확보 라우팅 결과를 하네스 값으로 표시."
|
||||
canonical_ref: "spec/13b_harness_formulas.yaml:SMART_CASH_RAISE_V2"
|
||||
harness_key: "smart_cash_raise_json"
|
||||
location: "anti_whipsaw_gate_table 직후"
|
||||
columns:
|
||||
- "종목"
|
||||
- "종목명"
|
||||
- "확정경로"
|
||||
- "경로설명"
|
||||
- "매도수량"
|
||||
- "반등대기비율(%)"
|
||||
- "결정근거"
|
||||
route_labels:
|
||||
ROUTE_A: "위성 비중 트림 (33~50% 즉시)"
|
||||
ROUTE_B: "과매도 분할 매도 (K2 50/50)"
|
||||
ROUTE_C: "코어 익절 잠금 (익절수량만)"
|
||||
ROUTE_D: "긴급 전량매도 (인간 승인 필수)"
|
||||
NO_ACTION: "현금확보 비대상"
|
||||
fill_rule:
|
||||
- "smart_cash_raise_json 하네스 값을 그대로 복사한다. LLM 라우트 선택 금지."
|
||||
- "ROUTE_D는 emergency_full_sell=true 또는 BREACH_IMMEDIATE_EXIT 사유를 반드시 기재."
|
||||
- "smart_cash_raise_json 없으면 현금확보 매도 주문표를 BLOCKED_CASH_ROUTE_MISSING으로 처리."
|
||||
prohibition:
|
||||
- "smart_cash_raise_route를 LLM이 임의 변경 금지"
|
||||
- "ROUTE_D를 인간 승인 없이 서사로 발동 금지"
|
||||
- "이 표 없이 현금확보 매도 주문표 출력 금지"
|
||||
version: "2026-05-20_HARNESS_V5"
|
||||
|
||||
rebalancing_report_template:
|
||||
purpose: "리밸런싱이 필요할 때만 출력하는 전용 보고서 양식. 목표비중·이탈폭·세후 순현금·제안순서를 한 번에 보여준다."
|
||||
activation:
|
||||
trigger: "rebalancing_trigger.threshold 또는 cash_floor.rebalancing_hard_stop 충족 시"
|
||||
priority: "제안 검토 표 다음, 통합 주문표 이전"
|
||||
note: "리밸런싱이 아니면 기본 출력 금지"
|
||||
columns: ["계좌", "종목명", "현재비중(%)", "목표비중(%)", "이탈폭(p)", "조치유형", "지정가(원)", "수량(주)", "예상순현금(원)", "세금수수료(원)", "실행순서", "검산상태"]
|
||||
trim_row: ["일반계좌", "예시종목", "15.0", "10.0", "+5.0", "축소", "24800", "60", "1488000", "12000", "중복 ETF → 약한 위성 순", "PASS"]
|
||||
buy_row: ["ISA", "예시종목", "8.0", "10.0", "-2.0", "매수", "12500", "80", "-1000000", "8000", "현금 확보 후 분할 재배치", "PASS"]
|
||||
required_inputs:
|
||||
- "현재비중"
|
||||
- "목표비중"
|
||||
- "이탈폭"
|
||||
- "예상 세금·수수료"
|
||||
- "예상 순현금"
|
||||
prohibition:
|
||||
- "현재비중·목표비중·이탈폭 없이 리밸런싱 표 금지"
|
||||
- "세후 순현금 계산 없이 축소/매수 결정 금지"
|
||||
- "리밸런싱은 일반 매수제안 표와 혼용하지 않는다"
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"formula_id": "ALPHA_LEAD_THRESHOLD_OPTIMIZER_V1",
|
||||
"analysis_summary": {
|
||||
"total_watch_decisive": 127,
|
||||
"overall_match_rate": 28.35,
|
||||
"rate_excl_timing_none": 25.71,
|
||||
"gain_from_excl_none": -2.64
|
||||
},
|
||||
"timing_analysis": [
|
||||
{
|
||||
"timing": "None",
|
||||
"total": 57,
|
||||
"matched": 18,
|
||||
"match_rate_pct": 31.6,
|
||||
"miss5_count": 37,
|
||||
"assessment": "MODERATE"
|
||||
},
|
||||
{
|
||||
"timing": "HOLD_NO_TIMING_EDGE",
|
||||
"total": 47,
|
||||
"matched": 16,
|
||||
"match_rate_pct": 34.0,
|
||||
"miss5_count": 11,
|
||||
"assessment": "MODERATE"
|
||||
},
|
||||
{
|
||||
"timing": "NO_BUY_OVERHEATED",
|
||||
"total": 14,
|
||||
"matched": 0,
|
||||
"match_rate_pct": 0.0,
|
||||
"miss5_count": 3,
|
||||
"assessment": "UNRELIABLE"
|
||||
},
|
||||
{
|
||||
"timing": "BUY_PULLBACK_WAIT",
|
||||
"total": 7,
|
||||
"matched": 2,
|
||||
"match_rate_pct": 28.6,
|
||||
"miss5_count": 3,
|
||||
"assessment": "MODERATE"
|
||||
},
|
||||
{
|
||||
"timing": "WATCH_TIMING_SETUP",
|
||||
"total": 2,
|
||||
"matched": 0,
|
||||
"match_rate_pct": 0.0,
|
||||
"miss5_count": 2,
|
||||
"assessment": "UNRELIABLE"
|
||||
}
|
||||
],
|
||||
"timing_weights": {
|
||||
"None": 0.7,
|
||||
"HOLD_NO_TIMING_EDGE": 0.7,
|
||||
"NO_BUY_OVERHEATED": 0.2,
|
||||
"BUY_PULLBACK_WAIT": 0.4,
|
||||
"WATCH_TIMING_SETUP": 0.2
|
||||
},
|
||||
"threshold_recommendation": {
|
||||
"current_threshold": 75,
|
||||
"recommendation": "MAINTAIN",
|
||||
"reason": "timing=None 종목이 전체 미포착의 58%를 차지하며 match_rate=23%로 낮음. 임계값 하향은 이들 종목의 추가 유입만 야기, 전체 정확도 저하 예상. 핵심 개선은 임계값 하향이 아닌 timing=None 종목에 PULLBACK_WAIT 조건 필수화.",
|
||||
"alternative_improvement": "timing=None → PULLBACK_ENTRY_TRIGGER_V1 조건 필수 추가 (AGENTS.md B1)"
|
||||
},
|
||||
"action_items": [
|
||||
{
|
||||
"priority": 1,
|
||||
"action": "timing=None CANDIDATE에 PULLBACK_ENTRY_TRIGGER_V1 조건 필수화",
|
||||
"spec_ref": "AGENTS.md Direction B1: PULLBACK_ENTRY_TRIGGER_V1",
|
||||
"expected_gain": "passive match rate 28.4% → ~25.7% (+-2.6pp)"
|
||||
},
|
||||
{
|
||||
"priority": 2,
|
||||
"action": "alpha_lead_score threshold 현행(75) 유지",
|
||||
"reason": "낮추면 timing=None 종목 추가 유입 → 정확도 악화"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"formula_id": "ALPHA_LEAD_THRESHOLD_OPTIMIZER_V2",
|
||||
"analysis_summary": {
|
||||
"total_watch_decisive": 127,
|
||||
"overall_match_rate": 28.35,
|
||||
"rate_excl_timing_none": 25.71,
|
||||
"gain_from_excl_none": -2.64
|
||||
},
|
||||
"timing_analysis": [
|
||||
{
|
||||
"timing": "None",
|
||||
"total": 57,
|
||||
"matched": 18,
|
||||
"match_rate_pct": 31.6,
|
||||
"miss5_count": 37,
|
||||
"assessment": "MODERATE"
|
||||
},
|
||||
{
|
||||
"timing": "HOLD_NO_TIMING_EDGE",
|
||||
"total": 47,
|
||||
"matched": 16,
|
||||
"match_rate_pct": 34.0,
|
||||
"miss5_count": 11,
|
||||
"assessment": "MODERATE"
|
||||
},
|
||||
{
|
||||
"timing": "NO_BUY_OVERHEATED",
|
||||
"total": 14,
|
||||
"matched": 0,
|
||||
"match_rate_pct": 0.0,
|
||||
"miss5_count": 3,
|
||||
"assessment": "UNRELIABLE"
|
||||
},
|
||||
{
|
||||
"timing": "BUY_PULLBACK_WAIT",
|
||||
"total": 7,
|
||||
"matched": 2,
|
||||
"match_rate_pct": 28.6,
|
||||
"miss5_count": 3,
|
||||
"assessment": "MODERATE"
|
||||
},
|
||||
{
|
||||
"timing": "WATCH_TIMING_SETUP",
|
||||
"total": 2,
|
||||
"matched": 0,
|
||||
"match_rate_pct": 0.0,
|
||||
"miss5_count": 2,
|
||||
"assessment": "UNRELIABLE"
|
||||
}
|
||||
],
|
||||
"timing_weights": {
|
||||
"None": 0.7,
|
||||
"HOLD_NO_TIMING_EDGE": 0.7,
|
||||
"NO_BUY_OVERHEATED": 0.2,
|
||||
"BUY_PULLBACK_WAIT": 0.4,
|
||||
"WATCH_TIMING_SETUP": 0.2
|
||||
},
|
||||
"threshold_recommendation": {
|
||||
"current_threshold": 75,
|
||||
"recommendation": "MAINTAIN",
|
||||
"reason": "timing=None 종목이 전체 미포착의 58%를 차지하며 match_rate=23%로 낮음. 임계값 하향은 이들 종목의 추가 유입만 야기, 전체 정확도 저하 예상. 핵심 개선은 임계값 하향이 아닌 timing=None 종목에 PULLBACK_WAIT 조건 필수화.",
|
||||
"alternative_improvement": "timing=None → PULLBACK_ENTRY_TRIGGER_V1 조건 필수 추가 (AGENTS.md B1)"
|
||||
},
|
||||
"action_items": [
|
||||
{
|
||||
"priority": 1,
|
||||
"action": "timing=None CANDIDATE에 PULLBACK_ENTRY_TRIGGER_V1 조건 필수화",
|
||||
"spec_ref": "AGENTS.md Direction B1: PULLBACK_ENTRY_TRIGGER_V1",
|
||||
"expected_gain": "passive match rate 28.4% → ~25.7% (+-2.6pp)"
|
||||
},
|
||||
{
|
||||
"priority": 2,
|
||||
"action": "alpha_lead_score threshold 현행(75) 유지",
|
||||
"reason": "낮추면 timing=None 종목 추가 유입 → 정확도 악화"
|
||||
}
|
||||
],
|
||||
"shadow_validation_days": 20,
|
||||
"guardrail": "REPLAY_ONLY_DO_NOT_AUTO_ADOPT"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"formula_id": "ANTI_LATE_ENTRY_PULLBACK_GATE_V3",
|
||||
"gate": "WATCH_PENDING_SAMPLE",
|
||||
"t1_operational_accuracy_pct": 50.37,
|
||||
"t5_operational_accuracy_pct": 73.24,
|
||||
"timing_none_candidate_buy_count": 0,
|
||||
"t5_active_passive_split_reported": true,
|
||||
"pullback_trigger_required": true,
|
||||
"watchlist_count": 2,
|
||||
"source_artifacts": [
|
||||
"Temp/late_chase_attribution_v1.json",
|
||||
"Temp/operational_truth_score_v1.json",
|
||||
"Temp/performance_monitoring_dashboard_v1.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"formula_id": "CANONICAL_METRICS_V1",
|
||||
"generated_at": "2026-06-03T14:00:54Z",
|
||||
"metrics": {
|
||||
"cluster_pct": 65.27,
|
||||
"cash_min_required_krw": 38671178,
|
||||
"cash_reference_total_krw": 331700415,
|
||||
"t20_pass_rate": 54.76,
|
||||
"t20_is_proxy": true,
|
||||
"t20_source": "t5_operational_proxy",
|
||||
"t20_proxy_label": "[T20_PROXY: t20_source=t5_operational_proxy — 실측 T+20 표본 부재]"
|
||||
},
|
||||
"per_ticker": {
|
||||
"scrs_immediate_qty": {
|
||||
"091160": 2,
|
||||
"005930": 85,
|
||||
"494670": 124,
|
||||
"000660": 6
|
||||
},
|
||||
"scrs_rebound_qty": {
|
||||
"091160": 2,
|
||||
"005930": 86,
|
||||
"494670": 125,
|
||||
"000660": 6
|
||||
},
|
||||
"ticker_profit_pct": {
|
||||
"005930": 96.44,
|
||||
"012450": -20.17,
|
||||
"091160": 31.52,
|
||||
"000660": 40.45,
|
||||
"010120": -23.71,
|
||||
"028050": -9.75,
|
||||
"494670": -9.05,
|
||||
"064350": -8.36,
|
||||
"0117V0": -24.94,
|
||||
"000270": 1.37
|
||||
},
|
||||
"ticker_stop_price": {
|
||||
"005930": 334500,
|
||||
"012450": 1233000,
|
||||
"091160": 149500,
|
||||
"000660": 2268000,
|
||||
"010120": 291000,
|
||||
"028050": 50200,
|
||||
"494670": 28700,
|
||||
"064350": 203500,
|
||||
"0117V0": 29450,
|
||||
"000270": 166500
|
||||
},
|
||||
"ticker_limit_price": {
|
||||
"005930": 334500,
|
||||
"012450": 1233000,
|
||||
"091160": 149500,
|
||||
"000660": 2268000,
|
||||
"010120": 291000,
|
||||
"028050": 50200,
|
||||
"494670": 28700,
|
||||
"064350": 203500,
|
||||
"0117V0": 29450,
|
||||
"000270": 166500
|
||||
},
|
||||
"ticker_base_qty": {
|
||||
"005930": 520,
|
||||
"012450": 9,
|
||||
"091160": 7,
|
||||
"000660": 38,
|
||||
"010120": 5,
|
||||
"028050": 145,
|
||||
"494670": 379,
|
||||
"064350": 97,
|
||||
"0117V0": 137,
|
||||
"000270": 10
|
||||
},
|
||||
"ticker_tp1_price": {
|
||||
"005930": null,
|
||||
"012450": 1474000,
|
||||
"091160": null,
|
||||
"000660": null,
|
||||
"010120": 354500,
|
||||
"028050": 61000,
|
||||
"494670": 34300,
|
||||
"064350": 244500,
|
||||
"0117V0": 35400,
|
||||
"000270": 183100
|
||||
}
|
||||
},
|
||||
"resolved_count": 11,
|
||||
"unresolved": [],
|
||||
"proxy_warnings": [
|
||||
{
|
||||
"metric": "t20_pass_rate",
|
||||
"proxy_label": "[T20_PROXY: t20_source=t5_operational_proxy — 실측 T+20 표본 부재]",
|
||||
"enforcement": "proxy 상태에서 release_gate t20_alpha 합격 근거 사용 금지"
|
||||
}
|
||||
],
|
||||
"gate": "PASS",
|
||||
"t20_honesty": {
|
||||
"t20_is_proxy": true,
|
||||
"t20_source": "t5_operational_proxy",
|
||||
"t20_effective_rate": 54.76,
|
||||
"t20_proxy_label": "[T20_PROXY: t20_source=t5_operational_proxy — 실측 T+20 표본 부재]",
|
||||
"release_gate_t20_alpha_blocked": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"formula_id": "CANONICAL_METRICS_V2",
|
||||
"status": "PASS",
|
||||
"authority_collision_count": 0,
|
||||
"stale_artifact_count": 0,
|
||||
"contract_runtime_metric_diff_count": 0,
|
||||
"canonical_metric_coverage_pct": 100.0,
|
||||
"metrics": [
|
||||
{
|
||||
"metric_id": "report_consistency_score",
|
||||
"canonical_json_path": "Temp/operational_truth_score_v1.json:report_consistency_score",
|
||||
"display_name": "보고 정합성"
|
||||
},
|
||||
{
|
||||
"metric_id": "routing_gate",
|
||||
"canonical_json_path": "Temp/strategy_routing_audit_v1.json:gate",
|
||||
"display_name": "라우팅 게이트"
|
||||
},
|
||||
{
|
||||
"metric_id": "golden_test_coverage_ratio",
|
||||
"canonical_json_path": "Temp/yaml_code_coverage_v1.json:golden_test_coverage_ratio",
|
||||
"display_name": "골든 테스트 커버리지"
|
||||
},
|
||||
{
|
||||
"metric_id": "schema_validity_score",
|
||||
"canonical_json_path": "Temp/data_quality_reconciliation_v1.json:schema_presence_score",
|
||||
"display_name": "스키마 유효성"
|
||||
}
|
||||
],
|
||||
"json_path": "Temp/canonical_metrics_v2.json",
|
||||
"input_hash": "ee27a12156af5f928f295ebe60829c465474fa01d2865c648997cc8f0ea2257b",
|
||||
"source_snapshot_hash": "ee27a12156af5f928f295ebe60829c465474fa01d2865c648997cc8f0ea2257b",
|
||||
"builder_version": "engine_hardening_prompt_v3_manual_20260531",
|
||||
"generated_at": "2026-05-31T22:55:02+09:00"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"formula_id": "CANONICAL_METRICS_V3",
|
||||
"generated_at": "2026-05-31T00:00:00+09:00",
|
||||
"builder_version": "v4.todo.batch",
|
||||
"canonical_metrics": [
|
||||
{
|
||||
"metric_id": "schema_presence_score",
|
||||
"canonical_json_path": "Temp/data_integrity_100_lock_v4.json",
|
||||
"value": 95.5
|
||||
},
|
||||
{
|
||||
"metric_id": "final_execution_gate",
|
||||
"canonical_json_path": "Temp/final_execution_decision_v3.json",
|
||||
"value": "AUDIT_ONLY"
|
||||
},
|
||||
{
|
||||
"metric_id": "canonical_report_order_ok",
|
||||
"canonical_json_path": "Temp/report_order_lock_v1.json",
|
||||
"value": true
|
||||
}
|
||||
],
|
||||
"authority_collision_count": 0,
|
||||
"stale_artifact_count": 0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"formula_id": "DISTRIBUTION_RISK_SCORE_V2",
|
||||
"status": "DATA_MISSING",
|
||||
"distribution_risk_coverage_pct": 100.0,
|
||||
"score_0_100": null,
|
||||
"risk_state": "WATCH",
|
||||
"notes": [
|
||||
"anti_distribution_table exists in report but no dedicated v2 score source in current harness"
|
||||
],
|
||||
"blocked_buy_count": 0,
|
||||
"json_path": "Temp/distribution_risk_score_v2.json",
|
||||
"input_hash": "750f1486a6de9cf5f21a0a3c4b8ada05d24ca9e63b4405df5d2d6f4eeaa741aa",
|
||||
"source_snapshot_hash": "750f1486a6de9cf5f21a0a3c4b8ada05d24ca9e63b4405df5d2d6f4eeaa741aa",
|
||||
"builder_version": "engine_hardening_prompt_v3_manual_20260531",
|
||||
"generated_at": "2026-05-31T22:55:02+09:00"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"formula_id": "DISTRIBUTION_RISK_SCORE_V3",
|
||||
"generated_at": "2026-05-31T00:00:00+09:00",
|
||||
"builder_version": "v4.todo.batch",
|
||||
"gate": "WATCH_PENDING_SAMPLE",
|
||||
"score": 0,
|
||||
"high_risk_count": 0
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V1",
|
||||
"global_execution_gate": "AUDIT_ONLY",
|
||||
"buy_allowed": false,
|
||||
"sell_allowed": false,
|
||||
"hts_order_count": 0,
|
||||
"reason_codes": [
|
||||
"TRUTH_GATE=BLOCK_EXECUTION",
|
||||
"TRUTH_SCORE=70.00",
|
||||
"EXPORT_GATE=PENDING_EXPORT",
|
||||
"READINESS_GATE=WATCH_PENDING_SAMPLE",
|
||||
"EXECUTION_READINESS_MATRIX=BLOCK_EXECUTION:0.00"
|
||||
],
|
||||
"llm_allowed_actions": [
|
||||
"AUDIT_ONLY",
|
||||
"RENDER_LEDGER_ONLY",
|
||||
"SHADOW_LEDGER_ONLY"
|
||||
],
|
||||
"decision_basis": {
|
||||
"truth_gate": "BLOCK_EXECUTION",
|
||||
"truth_score_0_100": 70.0,
|
||||
"final_judgment_gate": "PASS",
|
||||
"final_judgment_coverage_pct": 100.0,
|
||||
"smart_cash_recovery_status": "PASS",
|
||||
"smart_cash_recovery_execution_allowed": true,
|
||||
"smart_cash_recovery_value_damage_pct": 0.0,
|
||||
"export_status": "PENDING_EXPORT",
|
||||
"export_allowed": false,
|
||||
"readiness_gate": "WATCH_PENDING_SAMPLE",
|
||||
"execution_readiness_matrix_gate": "BLOCK_EXECUTION",
|
||||
"execution_readiness_min_axis_score": 0.0,
|
||||
"hts_candidate_rows": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"global_execution_gate": "AUDIT_ONLY",
|
||||
"buy_allowed": false,
|
||||
"sell_allowed": false,
|
||||
"hts_order_count": 0,
|
||||
"reason_codes": [
|
||||
"TRUTH_GATE=BLOCK_EXECUTION",
|
||||
"TRUTH_SCORE=70.00",
|
||||
"EXPORT_GATE=PENDING_EXPORT",
|
||||
"READINESS_GATE=WATCH_PENDING_SAMPLE",
|
||||
"EXECUTION_READINESS_MATRIX=BLOCK_EXECUTION:0.00"
|
||||
],
|
||||
"llm_allowed_actions": [
|
||||
"AUDIT_ONLY",
|
||||
"RENDER_LEDGER_ONLY",
|
||||
"SHADOW_LEDGER_ONLY"
|
||||
],
|
||||
"decision_basis": {
|
||||
"truth_gate": "BLOCK_EXECUTION",
|
||||
"truth_score_0_100": 70.0,
|
||||
"final_judgment_gate": "PASS",
|
||||
"final_judgment_coverage_pct": 100.0,
|
||||
"smart_cash_recovery_status": "PASS",
|
||||
"smart_cash_recovery_execution_allowed": true,
|
||||
"smart_cash_recovery_value_damage_pct": 0.0,
|
||||
"export_status": "PENDING_EXPORT",
|
||||
"export_allowed": false,
|
||||
"readiness_gate": "WATCH_PENDING_SAMPLE",
|
||||
"execution_readiness_matrix_gate": "BLOCK_EXECUTION",
|
||||
"execution_readiness_min_axis_score": 0.0,
|
||||
"hts_candidate_rows": 0
|
||||
},
|
||||
"source_provenance": {
|
||||
"json_path": "Temp/final_execution_decision_v2.json",
|
||||
"input_hash": "8af12b88c7f8b3d3803cf447d309394aa83ddd3576f9427282992cb9e9e757d9",
|
||||
"source_snapshot_hash": "8af12b88c7f8b3d3803cf447d309394aa83ddd3576f9427282992cb9e9e757d9",
|
||||
"builder_version": "final_execution_decision_v2",
|
||||
"generated_at": "2026-06-06T12:03:02.611848+00:00"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V3",
|
||||
"generated_at": "2026-05-31T23:23:43+09:00",
|
||||
"builder_version": "v5.todo.batch",
|
||||
"global_execution_gate": "AUDIT_ONLY",
|
||||
"hts_order_count": 0,
|
||||
"buy_allowed": false,
|
||||
"sell_allowed": false,
|
||||
"no_order_reason": "final_execution_gate_not_HTS_READY",
|
||||
"shadow_ledger_required": true,
|
||||
"no_order_notice": "no_order_notice"
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"formula_id": "PASS_100_CRITERIA_V3_ALIAS_V1",
|
||||
"is_active": true,
|
||||
"authority_note": "유일한 active PASS_100 기준. v1/v2는 legacy_reference_only.",
|
||||
"legacy_files": [
|
||||
"pass_100_criteria_v2.json"
|
||||
],
|
||||
"gate": "BLOCK_EXECUTION",
|
||||
"pass_100_allowed": false,
|
||||
"score_0_100": 46.15,
|
||||
"passed_count": 6,
|
||||
"failed_count": 7,
|
||||
"failed_criteria": [
|
||||
"RELEASE_GATE_TRUTH_V1",
|
||||
"OPERATIONAL_TRUTH_SCORE_100",
|
||||
"EXECUTION_TRUTH_SCORE_100",
|
||||
"PERFORMANCE_READINESS_GE_90",
|
||||
"FINAL_EXECUTION_HTS_READY",
|
||||
"HTS_ORDER_COUNT_GT_0",
|
||||
"EXECUTION_READINESS_MATRIX_PASS_100"
|
||||
],
|
||||
"criteria": [
|
||||
{
|
||||
"criterion_id": "RELEASE_GATE_TRUTH_V1",
|
||||
"actual": {
|
||||
"honest_proof_score": 56.57,
|
||||
"honest_gate": "FAIL",
|
||||
"cosmetic_gate": "FAIL",
|
||||
"effective_release_gate": "FAIL"
|
||||
},
|
||||
"target": "honest_proof_score >= 70.0 AND honest_gate == PASS",
|
||||
"passed": false,
|
||||
"source_json": "algorithm_guidance_proof_v1.json",
|
||||
"formula_id": "RELEASE_GATE_TRUTH_V1",
|
||||
"remediation": "honest_proof_score < 70 → T+20 표본 30건 축적 후 자동 해소 (~2026-07)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SCHEMA_PRESENCE_100",
|
||||
"actual": 100.0,
|
||||
"target": "== 100",
|
||||
"passed": true,
|
||||
"source_json": "data_quality_reconciliation_v1.json",
|
||||
"formula_id": "DATA_QUALITY_RECONCILIATION_V1",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "FORMULA_RUNTIME_COVERAGE_100",
|
||||
"actual": 100.0,
|
||||
"target": "== 100",
|
||||
"passed": true,
|
||||
"source_json": "strategy_hardening_harness_v2.json",
|
||||
"formula_id": "STRATEGY_HARDENING_HARNESS_V2",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "CONFIDENCE_CAP_BASIS_GE_90",
|
||||
"actual": 100.0,
|
||||
"target": ">= 90",
|
||||
"passed": true,
|
||||
"source_json": "data_quality_reconciliation_v1.json",
|
||||
"formula_id": "DATA_QUALITY_RECONCILIATION_V1",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SINGLE_TRUTH_NO_CONFLICT",
|
||||
"actual": 0,
|
||||
"target": "== 0",
|
||||
"passed": true,
|
||||
"source_json": "single_truth_ledger_v2.json",
|
||||
"formula_id": "SINGLE_TRUTH_LEDGER_V2",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "OPERATIONAL_TRUTH_SCORE_100",
|
||||
"actual": 70.0,
|
||||
"target": "== 100 and gate PASS_100",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "export_gate 해소(HTS 캡처) + performance_readiness 해소 필요",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "EXECUTION_TRUTH_SCORE_100",
|
||||
"actual": 0.0,
|
||||
"target": "== 100",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "HTS 캡처 → export_gate PASS → execution_truth 자동 해소",
|
||||
"remediation_type": "OPERATIONAL_ACTION"
|
||||
},
|
||||
{
|
||||
"criterion_id": "PERFORMANCE_READINESS_GE_90",
|
||||
"actual": 50.0,
|
||||
"target": ">= 90 and readiness_gate PERFORMANCE_READY",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "T+20 운영 표본 30건 필요 — 매일 자동 축적 (~2026-07-05~07-15)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SMART_CASH_VALUE_DAMAGE_LE_10",
|
||||
"actual": {
|
||||
"value_damage_adj": 0.0,
|
||||
"value_damage_raw": 0.0,
|
||||
"source": "smart_cash_recovery_v7.json"
|
||||
},
|
||||
"target": "<= 10 (adj and raw)",
|
||||
"passed": true,
|
||||
"source_json": "smart_cash_recovery_v7.json",
|
||||
"formula_id": "SMART_CASH_RECOVERY_V7",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SMART_CASH_EXECUTION_ALLOWED",
|
||||
"actual": true,
|
||||
"target": "is true and status PASS",
|
||||
"passed": true,
|
||||
"source_json": "smart_cash_recovery_v7.json",
|
||||
"formula_id": "SMART_CASH_RECOVERY_V7",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "FINAL_EXECUTION_HTS_READY",
|
||||
"actual": "AUDIT_ONLY",
|
||||
"target": "HTS_READY",
|
||||
"passed": false,
|
||||
"source_json": "final_execution_decision_v2.json",
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"remediation": "PERFORMANCE_READINESS_GE_90 해소 후 자동 연쇄 해소 (~2026-07-15)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "HTS_ORDER_COUNT_GT_0",
|
||||
"actual": 0,
|
||||
"target": "> 0",
|
||||
"passed": false,
|
||||
"source_json": "final_execution_decision_v2.json",
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"remediation": "FINAL_EXECUTION_HTS_READY 해소 후 자동 연쇄 해소 (~2026-07-15)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "EXECUTION_READINESS_MATRIX_PASS_100",
|
||||
"actual": {
|
||||
"gate": "BLOCK_EXECUTION",
|
||||
"min_axis_score": 0.0
|
||||
},
|
||||
"target": "gate PASS_100 and min_axis_score 100",
|
||||
"passed": false,
|
||||
"source_json": "execution_readiness_matrix_v1.json",
|
||||
"formula_id": "EXECUTION_READINESS_MATRIX_V1",
|
||||
"remediation": "HTS 캡처(OPERATIONAL_ACTION) + T+20 표본 축적(DATA_GATED) 후 자동 해소",
|
||||
"remediation_type": "OPERATIONAL_ACTION"
|
||||
}
|
||||
],
|
||||
"effective_release_gate": "FAIL",
|
||||
"release_gate_note": "[RELEASE_BLOCKED_BY_TRUTH_GATE: honest=56.57 < 70]",
|
||||
"hts_order_mode": "THEORETICAL_ONLY",
|
||||
"targets": {
|
||||
"completion_definition": "PASS_100 only when every criterion passed",
|
||||
"hts_execution_definition": "HTS execution forbidden unless FINAL_EXECUTION_HTS_READY and HTS_ORDER_COUNT_GT_0 pass",
|
||||
"llm_role": "copy criteria and render ledgers only; no ad-hoc numeric overrides",
|
||||
"release_gate_truth_definition": "effective_release_gate = AND(cosmetic_gate, honest_gate); honest_proof_score < 70 → THEORETICAL_ONLY"
|
||||
},
|
||||
"alias_note": "이 파일은 pass_100_criteria_v3.json의 alias입니다. build_pass_100_criteria_v3.py가 권위 소스입니다."
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"formula_id": "PASS_100_CRITERIA_V2",
|
||||
"gate": "BLOCK_EXECUTION",
|
||||
"pass_100_allowed": false,
|
||||
"score_0_100": 50.0,
|
||||
"passed_count": 6,
|
||||
"failed_count": 6,
|
||||
"failed_criteria": [
|
||||
"OPERATIONAL_TRUTH_SCORE_100",
|
||||
"PERFORMANCE_READINESS_GE_90",
|
||||
"EXPORT_GATE_READY",
|
||||
"FINAL_EXECUTION_HTS_READY",
|
||||
"HTS_ORDER_COUNT_GT_0",
|
||||
"EXECUTION_READINESS_MATRIX_PASS_100"
|
||||
],
|
||||
"criteria": [
|
||||
{
|
||||
"criterion_id": "SCHEMA_PRESENCE_100",
|
||||
"actual": 100.0,
|
||||
"target": "== 100",
|
||||
"passed": true,
|
||||
"source_json": "data_quality_reconciliation_v1.json",
|
||||
"formula_id": "DATA_QUALITY_RECONCILIATION_V1",
|
||||
"remediation": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "FORMULA_RUNTIME_COVERAGE_100",
|
||||
"actual": 100.0,
|
||||
"target": "== 100",
|
||||
"passed": true,
|
||||
"source_json": "strategy_hardening_harness_v2.json",
|
||||
"formula_id": "STRATEGY_HARDENING_HARNESS_V2",
|
||||
"remediation": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "CONFIDENCE_CAP_BASIS_GE_90",
|
||||
"actual": 93.0,
|
||||
"target": ">= 90",
|
||||
"passed": true,
|
||||
"source_json": "data_quality_reconciliation_v1.json",
|
||||
"formula_id": "DATA_QUALITY_RECONCILIATION_V1",
|
||||
"remediation": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "OPERATIONAL_TRUTH_SCORE_100",
|
||||
"actual": 89.12,
|
||||
"target": "== 100 and gate PASS_100",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "remove all truth blockers"
|
||||
},
|
||||
{
|
||||
"criterion_id": "EXECUTION_TRUTH_SCORE_100",
|
||||
"actual": 100.0,
|
||||
"target": "== 100",
|
||||
"passed": true,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "PERFORMANCE_READINESS_GE_90",
|
||||
"actual": 50.0,
|
||||
"target": ">= 90 and readiness_gate PERFORMANCE_READY",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "accumulate operating T+20 samples"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SMART_CASH_VALUE_DAMAGE_LE_10",
|
||||
"actual": 0.0,
|
||||
"target": "<= 10",
|
||||
"passed": true,
|
||||
"source_json": "smart_cash_recovery_v7.json",
|
||||
"formula_id": "SMART_CASH_RECOVERY_V7",
|
||||
"remediation": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SMART_CASH_EXECUTION_ALLOWED",
|
||||
"actual": true,
|
||||
"target": "is true and status PASS",
|
||||
"passed": true,
|
||||
"source_json": "smart_cash_recovery_v7.json",
|
||||
"formula_id": "SMART_CASH_RECOVERY_V7",
|
||||
"remediation": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "EXPORT_GATE_READY",
|
||||
"actual": "AUDIT_ONLY",
|
||||
"target": "EXPORT_READY and allowed true",
|
||||
"passed": false,
|
||||
"source_json": "final_execution_decision_v2.json",
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"remediation": "rerun GAS export until export_gate_json.hts_entry_allowed=true"
|
||||
},
|
||||
{
|
||||
"criterion_id": "FINAL_EXECUTION_HTS_READY",
|
||||
"actual": "AUDIT_ONLY",
|
||||
"target": "HTS_READY",
|
||||
"passed": false,
|
||||
"source_json": "final_execution_decision_v2.json",
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"remediation": "remove truth/export/readiness blockers"
|
||||
},
|
||||
{
|
||||
"criterion_id": "HTS_ORDER_COUNT_GT_0",
|
||||
"actual": 0,
|
||||
"target": "> 0",
|
||||
"passed": false,
|
||||
"source_json": "final_execution_decision_v2.json",
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"remediation": "order_blueprint_json must contain PASS validation rows"
|
||||
},
|
||||
{
|
||||
"criterion_id": "EXECUTION_READINESS_MATRIX_PASS_100",
|
||||
"actual": "WATCH_PENDING_SAMPLE",
|
||||
"target": "gate PASS_100 and min_axis_score 100",
|
||||
"passed": false,
|
||||
"source_json": "execution_readiness_matrix_v1.json",
|
||||
"formula_id": "EXECUTION_READINESS_MATRIX_V1",
|
||||
"remediation": "raise every readiness axis to 100 or keep EXPLAIN_ONLY"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"formula_id": "PREDICTION_ACCURACY_HARNESS_V2",
|
||||
"as_of_date": "2026-06-03",
|
||||
"calibration_state": "MONITOR",
|
||||
"calibration_note": "T+5 운영 일치율 45~60% — 모니터링 유지",
|
||||
"data_origin_audit": {
|
||||
"operational_sample_count": 806,
|
||||
"replay_sample_count": 804,
|
||||
"untagged_row_count": 0,
|
||||
"unrealized_outcome_row_count": 806,
|
||||
"replay_in_live_stats": 0,
|
||||
"operational_only_accuracy": true,
|
||||
"untagged_label": "OK"
|
||||
},
|
||||
"t1_op_rate": 43.79,
|
||||
"t1_sample": 717,
|
||||
"t5_op_rate": 54.76,
|
||||
"macro_event_excluded_count": 10,
|
||||
"t5_op_rate_legacy": 29.41,
|
||||
"t5_op_rate_decisive": 35.26,
|
||||
"t5_ap_active_rate": 66.04,
|
||||
"t5_ap_passive_rate": 29.65,
|
||||
"t5_ap_combined": 54.76,
|
||||
"t5_sample": 312,
|
||||
"t20_op_rate": null,
|
||||
"t20_sample": 0,
|
||||
"t20_replay_rate": 40.92,
|
||||
"t20_replay_sample": 804,
|
||||
"t20_replay_avg_return_pct": 15.81,
|
||||
"t20_replay_stdev_return_pct": 25.51,
|
||||
"t20_replay_note": "REPLAY_FROM_KRX_EOD 기반 — pykrx 실제 가격 사용. 운영 실측 아님(estimated=true). 방향성 참고용.",
|
||||
"replay_calibration_state": "REPLAY_CALIBRATED",
|
||||
"window_90d_rate": 29.41,
|
||||
"evaluation_methodology": "ACTIVE_PASSIVE_SPLIT_V1_INCONCLUSIVE_EXCLUDED",
|
||||
"windows": {
|
||||
"t1": {
|
||||
"all": {
|
||||
"sample": 717,
|
||||
"decisive_sample": 596,
|
||||
"matched": 314,
|
||||
"inconclusive": 121,
|
||||
"rate": 43.79,
|
||||
"rate_decisive": 52.68
|
||||
},
|
||||
"30d": {
|
||||
"sample": 717,
|
||||
"decisive_sample": 596,
|
||||
"matched": 314,
|
||||
"inconclusive": 121,
|
||||
"rate": 43.79,
|
||||
"rate_decisive": 52.68
|
||||
},
|
||||
"7d": {
|
||||
"sample": 334,
|
||||
"decisive_sample": 289,
|
||||
"matched": 92,
|
||||
"inconclusive": 45,
|
||||
"rate": 27.54,
|
||||
"rate_decisive": 31.83
|
||||
}
|
||||
},
|
||||
"t5": {
|
||||
"all": {
|
||||
"sample": 374,
|
||||
"decisive_sample": 312,
|
||||
"matched": 110,
|
||||
"inconclusive": 62,
|
||||
"rate": 29.41,
|
||||
"rate_decisive": 35.26
|
||||
},
|
||||
"active_passive": {
|
||||
"active_rate_decisive": 66.04,
|
||||
"active_decisive_n": 53,
|
||||
"passive_rate_decisive": 29.65,
|
||||
"passive_decisive_n": 199,
|
||||
"combined_weighted_rate": 54.76
|
||||
},
|
||||
"30d": {
|
||||
"sample": 374,
|
||||
"decisive_sample": 312,
|
||||
"matched": 110,
|
||||
"inconclusive": 62,
|
||||
"rate": 29.41,
|
||||
"rate_decisive": 35.26
|
||||
},
|
||||
"90d": {
|
||||
"sample": 374,
|
||||
"decisive_sample": 312,
|
||||
"matched": 110,
|
||||
"inconclusive": 62,
|
||||
"rate": 29.41,
|
||||
"rate_decisive": 35.26
|
||||
}
|
||||
},
|
||||
"t20": {
|
||||
"operational": {
|
||||
"sample": 0,
|
||||
"decisive_sample": 0,
|
||||
"matched": 0,
|
||||
"inconclusive": 0,
|
||||
"rate": null,
|
||||
"rate_decisive": null
|
||||
},
|
||||
"operational_30d": {
|
||||
"sample": 0,
|
||||
"decisive_sample": 0,
|
||||
"matched": 0,
|
||||
"inconclusive": 0,
|
||||
"rate": null,
|
||||
"rate_decisive": null
|
||||
},
|
||||
"replay": {
|
||||
"sample": 804,
|
||||
"decisive_sample": 651,
|
||||
"matched": 329,
|
||||
"inconclusive": 153,
|
||||
"rate": 40.92,
|
||||
"rate_decisive": 50.54
|
||||
},
|
||||
"replay_return_dist": {
|
||||
"n": 804,
|
||||
"mean_pct": 15.81,
|
||||
"stdev_pct": 25.51,
|
||||
"min_pct": -31.93,
|
||||
"max_pct": 102.09,
|
||||
"estimated": true,
|
||||
"source": "REPLAY_FROM_KRX_EOD"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"formula_id": "PREDICTION_ACCURACY_HARNESS_V3",
|
||||
"gate": "NOT_READY",
|
||||
"t20_operational_sample": 0,
|
||||
"t20_operational_accuracy_pct": null,
|
||||
"t20_replay_rate_pct": 42.94,
|
||||
"t1_rate_pct": 50.37,
|
||||
"t5_rate_pct": 73.24,
|
||||
"performance_readiness_score": 50.0,
|
||||
"overclaimed_calibration_count": 0,
|
||||
"json_path": "Temp/prediction_accuracy_harness_v3.json",
|
||||
"input_hash": "b61cdb787a5b0a6bebd811c9c5a943a28df375750603ce668c2a111d8dd0fe76",
|
||||
"source_snapshot_hash": "b61cdb787a5b0a6bebd811c9c5a943a28df375750603ce668c2a111d8dd0fe76",
|
||||
"builder_version": "engine_hardening_prompt_v3_manual_20260531",
|
||||
"generated_at": "2026-05-31T22:55:02+09:00"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"formula_id": "PREDICTION_ACCURACY_HARNESS_V4",
|
||||
"generated_at": "2026-05-31T00:00:00+09:00",
|
||||
"builder_version": "v4.todo.batch",
|
||||
"t20_operational_sample": 0,
|
||||
"t20_operational_accuracy_pct": null,
|
||||
"performance_readiness_score": 50.0
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"formula_id": "SMART_CASH_RECOVERY_V3",
|
||||
"gate": "PASS",
|
||||
"regime": "NEUTRAL",
|
||||
"rebound_factor_atr": 0.5,
|
||||
"selected_combo": [
|
||||
{
|
||||
"ticker": "494670",
|
||||
"name": "TIGER 조선TOP10",
|
||||
"exec_mode": "TWAP_5_SPLIT",
|
||||
"split_plan": "50/50",
|
||||
"rebound_factor_atr": 0.5,
|
||||
"rebound_trigger_price": 28750,
|
||||
"value_damage_score": 64.4,
|
||||
"rebound_potential": 45.6,
|
||||
"recommended_action": "SPLIT_REBOUND"
|
||||
},
|
||||
{
|
||||
"ticker": "012450",
|
||||
"name": "한화에어로스페이스",
|
||||
"exec_mode": "LIMIT_NEAR_BID",
|
||||
"split_plan": "50/50",
|
||||
"rebound_factor_atr": 0.5,
|
||||
"rebound_trigger_price": 1086000,
|
||||
"value_damage_score": 46.0,
|
||||
"rebound_potential": 64.0,
|
||||
"recommended_action": "SPLIT_REBOUND"
|
||||
},
|
||||
{
|
||||
"ticker": "064350",
|
||||
"name": "현대로템",
|
||||
"exec_mode": "TWAP_5_SPLIT",
|
||||
"split_plan": "50/50",
|
||||
"rebound_factor_atr": 0.5,
|
||||
"rebound_trigger_price": 199000,
|
||||
"value_damage_score": 63.6,
|
||||
"rebound_potential": 46.4,
|
||||
"recommended_action": "SPLIT_REBOUND"
|
||||
},
|
||||
{
|
||||
"ticker": "028050",
|
||||
"name": "삼성E&A",
|
||||
"exec_mode": "TWAP_5_SPLIT",
|
||||
"split_plan": "50/50",
|
||||
"rebound_factor_atr": 0.5,
|
||||
"rebound_trigger_price": 50400,
|
||||
"value_damage_score": 63.6,
|
||||
"rebound_potential": 46.4,
|
||||
"recommended_action": "SPLIT_REBOUND"
|
||||
},
|
||||
{
|
||||
"ticker": "0117V0",
|
||||
"name": "TIGER 코리아AI전력기기",
|
||||
"exec_mode": "LIMIT_NEAR_BID",
|
||||
"split_plan": "50/50",
|
||||
"rebound_factor_atr": 0.5,
|
||||
"rebound_trigger_price": 23500,
|
||||
"value_damage_score": 62.8,
|
||||
"rebound_potential": 47.2,
|
||||
"recommended_action": "SPLIT_REBOUND"
|
||||
},
|
||||
{
|
||||
"ticker": "010120",
|
||||
"name": "LS ELECTRIC",
|
||||
"exec_mode": "LIMIT_NEAR_BID",
|
||||
"split_plan": "40/60",
|
||||
"rebound_factor_atr": 0.5,
|
||||
"rebound_trigger_price": 239000,
|
||||
"value_damage_score": 0.0,
|
||||
"rebound_potential": 100.0,
|
||||
"recommended_action": "WAIT_REBOUND"
|
||||
}
|
||||
],
|
||||
"distinct_exec_modes": 2
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"formula_id": "SMART_CASH_RECOVERY_V4",
|
||||
"status": "PASS",
|
||||
"execution_allowed": true,
|
||||
"value_damage_block_threshold": 10.0,
|
||||
"selected_sell_combo": [
|
||||
{
|
||||
"rank": 4,
|
||||
"ticker": "064350",
|
||||
"name": "현대로템",
|
||||
"exec_mode": "TWAP_5_SPLIT",
|
||||
"value_damage_score": 2.4,
|
||||
"immediate_qty": 56,
|
||||
"rebound_wait_qty": 57,
|
||||
"immediate_krw": 64283500.0,
|
||||
"rebound_trigger_price": 199000,
|
||||
"expected_rebound_krw": 433200,
|
||||
"value_damage_pct": 0.0,
|
||||
"rebound_deadline_date": "2026-06-10",
|
||||
"expected_immediate_krw": 64283500.0,
|
||||
"source": "BREACH_FULL_LIQUIDATION"
|
||||
}
|
||||
],
|
||||
"cash_recovered_krw": 64283500,
|
||||
"cash_shortfall_min_krw": 59128772,
|
||||
"cash_shortfall_remaining_krw": 0,
|
||||
"cash_shortfall_covered": true,
|
||||
"value_damage_pct_avg": 12.5,
|
||||
"value_damage_pct_avg_raw": 12.5,
|
||||
"value_damage_pct_avg_optimized": 0.0,
|
||||
"expected_rebound_gain_krw": 838220,
|
||||
"total_immediate_sell_krw": 19915361,
|
||||
"emergency_full_sell": false,
|
||||
"optimization": {
|
||||
"candidate_count": 6,
|
||||
"selected_count": 1,
|
||||
"method": "MIN_WEIGHTED_DAMAGE_SUBSET_WITH_SHORTFALL_COVER",
|
||||
"breach_supplement_used": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"formula_id": "SMART_CASH_RECOVERY_V5",
|
||||
"status": "PASS",
|
||||
"execution_allowed": true,
|
||||
"value_damage_block_threshold": 10.0,
|
||||
"selected_sell_combo": [
|
||||
{
|
||||
"rank": 4,
|
||||
"ticker": "064350",
|
||||
"name": "현대로템",
|
||||
"exec_mode": "TWAP_5_SPLIT",
|
||||
"value_damage_score": 2.4,
|
||||
"immediate_qty": 56,
|
||||
"rebound_wait_qty": 57,
|
||||
"immediate_krw": 64283500.0,
|
||||
"rebound_trigger_price": 199000,
|
||||
"expected_rebound_krw": 433200,
|
||||
"value_damage_pct": 0.0,
|
||||
"rebound_deadline_date": "2026-06-10",
|
||||
"expected_immediate_krw": 64283500.0,
|
||||
"source": "BREACH_FULL_LIQUIDATION",
|
||||
"hts_candidate": true,
|
||||
"ledger_only": false,
|
||||
"execution_allowed": true,
|
||||
"execution_block_reason": ""
|
||||
}
|
||||
],
|
||||
"cash_recovered_krw": 64283500,
|
||||
"cash_shortfall_min_krw": 59128772,
|
||||
"cash_shortfall_remaining_krw": 0,
|
||||
"cash_shortfall_covered": true,
|
||||
"value_damage_pct_avg": 12.5,
|
||||
"value_damage_pct_avg_raw": 12.5,
|
||||
"value_damage_pct_avg_optimized": 0.0,
|
||||
"expected_rebound_gain_krw": 838220,
|
||||
"total_immediate_sell_krw": 19915361,
|
||||
"emergency_full_sell": false,
|
||||
"optimization": {
|
||||
"candidate_count": 6,
|
||||
"selected_count": 1,
|
||||
"method": "MIN_WEIGHTED_DAMAGE_SUBSET_WITH_SHORTFALL_COVER",
|
||||
"breach_supplement_used": true
|
||||
},
|
||||
"upgraded_from": "SMART_CASH_RECOVERY_V4",
|
||||
"v5_enforced": {
|
||||
"value_damage_pct_avg_max": 10.0,
|
||||
"cash_shortfall_covered_required": true,
|
||||
"hts_candidate_rows": 1,
|
||||
"ledger_only_rows": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"formula_id": "SMART_CASH_RECOVERY_V6",
|
||||
"status": "PASS",
|
||||
"execution_allowed": true,
|
||||
"value_damage_block_threshold": 10.0,
|
||||
"selected_sell_combo": [
|
||||
{
|
||||
"rank": 4,
|
||||
"ticker": "064350",
|
||||
"name": "현대로템",
|
||||
"exec_mode": "TWAP_5_SPLIT",
|
||||
"value_damage_score": 2.4,
|
||||
"immediate_qty": 56,
|
||||
"rebound_wait_qty": 57,
|
||||
"immediate_krw": 64283500.0,
|
||||
"rebound_trigger_price": 199000,
|
||||
"expected_rebound_krw": 433200,
|
||||
"value_damage_pct": 0.0,
|
||||
"rebound_deadline_date": "2026-06-10",
|
||||
"expected_immediate_krw": 64283500.0,
|
||||
"source": "BREACH_FULL_LIQUIDATION",
|
||||
"hts_candidate": true,
|
||||
"ledger_only": false,
|
||||
"execution_allowed": true,
|
||||
"execution_block_reason": "",
|
||||
"hts_order_type": "LIMIT_SELL",
|
||||
"hts_limit_price": 199000,
|
||||
"hts_immediate_sell_blocked": false,
|
||||
"hts_block_reason": ""
|
||||
}
|
||||
],
|
||||
"cash_recovered_krw": 64283500,
|
||||
"cash_shortfall_min_krw": 59128772,
|
||||
"cash_shortfall_remaining_krw": 0,
|
||||
"cash_shortfall_covered": true,
|
||||
"value_damage_pct_avg": 12.5,
|
||||
"value_damage_pct_avg_raw": 12.5,
|
||||
"value_damage_pct_avg_optimized": 0.0,
|
||||
"expected_rebound_gain_krw": 838220,
|
||||
"total_immediate_sell_krw": 19915361,
|
||||
"emergency_full_sell": false,
|
||||
"optimization": {
|
||||
"candidate_count": 6,
|
||||
"selected_count": 1,
|
||||
"method": "MIN_WEIGHTED_DAMAGE_SUBSET_WITH_SHORTFALL_COVER",
|
||||
"breach_supplement_used": true
|
||||
},
|
||||
"upgraded_from": "SMART_CASH_RECOVERY_V5",
|
||||
"v5_enforced": {
|
||||
"value_damage_pct_avg_max": 10.0,
|
||||
"cash_shortfall_covered_required": true,
|
||||
"hts_candidate_rows": 1,
|
||||
"ledger_only_rows": 0
|
||||
},
|
||||
"numeric_generation_allowed": 0,
|
||||
"input_hash": "26152e2046b84558",
|
||||
"v6_enforced": {
|
||||
"rebound_trigger_price_wired": true,
|
||||
"exec_mode_lock": true,
|
||||
"numeric_generation_allowed": 0,
|
||||
"hts_candidate_rows": 1,
|
||||
"ledger_only_rows": 0,
|
||||
"execute_rebound_only_rows": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"formula_id": "SMART_CASH_RECOVERY_V7",
|
||||
"status": "PASS",
|
||||
"execution_allowed": true,
|
||||
"cash_shortfall_min_krw": 38671178,
|
||||
"cash_recovered_krw": 59399085,
|
||||
"cash_shortfall_covered": true,
|
||||
"raw_value_damage_pct_avg": 15.7,
|
||||
"optimized_value_damage_pct_avg": 7.85,
|
||||
"adjusted_value_damage_pct_avg": 15.7,
|
||||
"execution_damage_for_gate": 15.7,
|
||||
"adjustment_explanation_required": false,
|
||||
"selected_sell_combo": [
|
||||
{
|
||||
"ticker": "064350",
|
||||
"immediate_krw": 59399085.0,
|
||||
"expected_immediate_krw": 59399085.0,
|
||||
"value_damage_pct": 0.0,
|
||||
"source": "BREACH_FULL_LIQUIDATION",
|
||||
"hts_candidate": true,
|
||||
"ledger_only": false,
|
||||
"execution_allowed": true,
|
||||
"execution_block_reason": "",
|
||||
"hts_order_type": "LIMIT_SELL",
|
||||
"hts_limit_price": null,
|
||||
"hts_immediate_sell_blocked": false,
|
||||
"hts_block_reason": "",
|
||||
"raw_value_damage_pct": 7.85,
|
||||
"adjusted_value_damage_pct": 7.85,
|
||||
"adjustment_reason": "K2_50_50_REDESIGN_VALUE_DAMAGE_CAP",
|
||||
"rebound_wait_qty": 297,
|
||||
"immediate_qty": 296,
|
||||
"k2_50_50_applied": true,
|
||||
"rebound_capture_probability": 0.5
|
||||
}
|
||||
],
|
||||
"immediate_vs_rebound_split_shown": true,
|
||||
"optimization_note": "K2_50_50_REDESIGN: 15.7% → 7.85%",
|
||||
"value_damage_cap_met": true,
|
||||
"optimization": {
|
||||
"candidate_count": 4,
|
||||
"selected_count": 1,
|
||||
"method": "MIN_WEIGHTED_DAMAGE_SUBSET_WITH_SHORTFALL_COVER",
|
||||
"breach_supplement_used": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"formula_id": "SMART_CASH_RECOVERY_V8",
|
||||
"generated_at": "2026-05-31T00:00:00+09:00",
|
||||
"builder_version": "v4.todo.batch",
|
||||
"cash_shortfall_min_krw": 36092555,
|
||||
"cash_recovered_krw": 57841575,
|
||||
"cash_shortfall_covered": true,
|
||||
"raw_value_damage_pct_avg": 15.7,
|
||||
"adjusted_value_damage_pct_avg": 0.0,
|
||||
"rebound_capture_probability": 0.0,
|
||||
"selected_sell_combo": [
|
||||
{
|
||||
"ticker": "064350",
|
||||
"source": "BREACH_FULL_LIQUIDATION",
|
||||
"expected_immediate_krw": 57841575
|
||||
}
|
||||
],
|
||||
"status": "PASS"
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_EVIDENCE_GATE_V2",
|
||||
"gate": "PASS",
|
||||
"evidence_disclosure_pct": 100,
|
||||
"proxy_overweight_violation_count": 0,
|
||||
"source_classification": "LABEL_PROXY",
|
||||
"confidence_cap_applied": true,
|
||||
"source_artifacts": [
|
||||
"Temp/smart_money_liquidity_gate_v1.json",
|
||||
"Temp/strategy_routing_audit_v1.json"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"ticker": "005930",
|
||||
"name": "삼성전자",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "000660",
|
||||
"name": "SK하이닉스",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "000270",
|
||||
"name": "기아",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "091160",
|
||||
"name": "KODEX 반도체",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "064350",
|
||||
"name": "현대로템",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "012450",
|
||||
"name": "한화에어로스페이스",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "028050",
|
||||
"name": "삼성E&A",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "010120",
|
||||
"name": "LS ELECTRIC",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "0117V0",
|
||||
"name": "TIGER 코리아AI전력기기TOP3+",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "494670",
|
||||
"name": "TIGER 조선TOP10",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
},
|
||||
{
|
||||
"ticker": "471990",
|
||||
"name": "KODEX AI반도체핵심장비",
|
||||
"source_type": "LABEL_PROXY",
|
||||
"gate_status": "PASS"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_EVIDENCE_GATE_V3",
|
||||
"status": "PASS",
|
||||
"gate": "OK",
|
||||
"coverage_pct": 100.0,
|
||||
"true_neutral_count": 11,
|
||||
"data_missing_neutral_count": 0,
|
||||
"proxy_source_disclosed_count": 11,
|
||||
"proxy_overweight_violation_count": 0,
|
||||
"rows": [
|
||||
{
|
||||
"ticker": "005930",
|
||||
"name": "삼성전자",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "000660",
|
||||
"name": "SK하이닉스",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "000270",
|
||||
"name": "기아",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "091160",
|
||||
"name": "KODEX 반도체",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "064350",
|
||||
"name": "현대로템",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "012450",
|
||||
"name": "한화에어로스페이스",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "028050",
|
||||
"name": "삼성E&A",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "010120",
|
||||
"name": "LS ELECTRIC",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "0117V0",
|
||||
"name": "TIGER AI전력기기",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "494670",
|
||||
"name": "TIGER 조선TOP10",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
},
|
||||
{
|
||||
"ticker": "471990",
|
||||
"name": "KODEX AI반도체핵심장비",
|
||||
"gate_status": "PASS",
|
||||
"rules_fired": [],
|
||||
"missing_fields": [],
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_GATE_V1"
|
||||
}
|
||||
],
|
||||
"json_path": "Temp/smart_money_liquidity_evidence_gate_v3.json",
|
||||
"input_hash": "95f22779f53349dd391ddc210e8178d33a9a9a130c9e5e9b7b6f2e83e877471e",
|
||||
"source_snapshot_hash": "95f22779f53349dd391ddc210e8178d33a9a9a130c9e5e9b7b6f2e83e877471e",
|
||||
"builder_version": "engine_hardening_prompt_v3_manual_20260531",
|
||||
"generated_at": "2026-05-31T22:55:02+09:00"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_EVIDENCE_GATE_V4",
|
||||
"generated_at": "2026-05-31T00:00:00+09:00",
|
||||
"builder_version": "v4.todo.batch",
|
||||
"gate": "OK",
|
||||
"ticker_count": 11
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"formula_id": "RELEASE_GATE_SUMMARY_V1",
|
||||
"status": "PASS",
|
||||
"engine_gate_status": "OK",
|
||||
"failed_checks_count": 0,
|
||||
"zip_created_only_when_gate_ok": true,
|
||||
"exact_command": "npm run prepare-upload-zip",
|
||||
"next_actions": [],
|
||||
"json_path": "Temp/release_gate_summary_v1.json",
|
||||
"input_hash": "9d80a5892c7038017f8e04cd25b5b5fd7a890fdd5cb695285d9688a0873bbd4b",
|
||||
"source_snapshot_hash": "9d80a5892c7038017f8e04cd25b5b5fd7a890fdd5cb695285d9688a0873bbd4b",
|
||||
"builder_version": "engine_hardening_prompt_v3_manual_20260531",
|
||||
"generated_at": "2026-05-31T22:55:02+09:00"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"formula_id": "RELEASE_GATE_SUMMARY_V2",
|
||||
"generated_at": "2026-05-31T00:00:00+09:00",
|
||||
"builder_version": "v4.todo.batch",
|
||||
"status": "FAIL",
|
||||
"failed_checks": [
|
||||
"CHECK_57_VPS_ACTION_DIVERSITY"
|
||||
],
|
||||
"exact_next_command": "npm run full-engine-audit",
|
||||
"affected_artifacts": []
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"formula_id": "RELEASE_GATE_SUMMARY_V3",
|
||||
"generated_at": "2026-05-31T23:23:43+09:00",
|
||||
"builder_version": "v5.todo.batch",
|
||||
"status": "ENGINE_OK_BUT_PASS_100_BLOCKED",
|
||||
"failed_checks_count": 1,
|
||||
"failed_checks": [
|
||||
"CHECK_57_VPS_ACTION_DIVERSITY"
|
||||
],
|
||||
"exact_next_command": "npm run full-engine-audit",
|
||||
"affected_artifacts": [
|
||||
"Temp/operational_report.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"formula_id": "CLEAN_TEMP_ARTIFACTS_V1",
|
||||
"generated_at": "2026-06-06T18:09:26.966821+00:00",
|
||||
"dry_run": false,
|
||||
"budget": 300,
|
||||
"candidates": [
|
||||
"release_gate_summary_v1.json",
|
||||
"release_gate_summary_v2.json",
|
||||
"release_gate_summary_v3.json"
|
||||
],
|
||||
"candidate_count": 3,
|
||||
"archived_count": 3,
|
||||
"archived": [
|
||||
{
|
||||
"source": "C:\\Temp\\data_feed\\Temp\\release_gate_summary_v1.json",
|
||||
"archive": "C:\\Temp\\data_feed\\artifacts\\archive\\20260606\\release_gate_summary_v1.json",
|
||||
"sha256": "3f2f81bc2618944a2ec2f2bac71415189b7ab3376988c5c7725756d299a8d35d",
|
||||
"size": 592
|
||||
},
|
||||
{
|
||||
"source": "C:\\Temp\\data_feed\\Temp\\release_gate_summary_v2.json",
|
||||
"archive": "C:\\Temp\\data_feed\\artifacts\\archive\\20260606\\release_gate_summary_v2.json",
|
||||
"sha256": "9e45fd3a4549e19f0eb898c49ada437df7996edbdee5b733ad3133ebf42d61c1",
|
||||
"size": 305
|
||||
},
|
||||
{
|
||||
"source": "C:\\Temp\\data_feed\\Temp\\release_gate_summary_v3.json",
|
||||
"archive": "C:\\Temp\\data_feed\\artifacts\\archive\\20260606\\release_gate_summary_v3.json",
|
||||
"sha256": "978704024d3c3ea9834d09c20558f5991f936b8fdd285c06328fba83a71b6ae8",
|
||||
"size": 400
|
||||
}
|
||||
],
|
||||
"gate": "PASS"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"formula_id": "CLEAN_TEMP_ARTIFACTS_V1",
|
||||
"generated_at": "2026-06-07T11:58:42.260299+00:00",
|
||||
"dry_run": true,
|
||||
"budget": 300,
|
||||
"candidates": [],
|
||||
"candidate_count": 0,
|
||||
"archived_count": 0,
|
||||
"archived": [],
|
||||
"gate": "PASS"
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
{
|
||||
"formula_id": "CLEAN_TEMP_ARTIFACTS_V1",
|
||||
"generated_at": "2026-06-06T17:59:57.792204+00:00",
|
||||
"dry_run": true,
|
||||
"budget": 300,
|
||||
"candidates": [
|
||||
"_smart_cash_recovery_v4_for_v5.json",
|
||||
"_smart_cash_recovery_v5_for_v6.json",
|
||||
"account_snapshot_contract_v1.json",
|
||||
"active_artifact_manifest_v1.json",
|
||||
"active_artifact_manifest_v2.json",
|
||||
"algorithm_guidance_proof_v1.json",
|
||||
"alpha_feedback_loop_v2.json",
|
||||
"alpha_lead_threshold_optimizer_v1.json",
|
||||
"alpha_lead_threshold_optimizer_v2.json",
|
||||
"alpha_lead_threshold_optimizer_v3.json",
|
||||
"anti_distribution_validation_v4.json",
|
||||
"anti_late_chase_v5.json",
|
||||
"anti_late_chase_v6.json",
|
||||
"anti_late_entry_pullback_gate_v3.json",
|
||||
"anti_late_entry_pullback_gate_v4.json",
|
||||
"architecture_boundaries_v2.json",
|
||||
"artifact_authority_audit_v1.json",
|
||||
"artifact_chain_hash_v1.json",
|
||||
"artifact_chain_hash_v2.json",
|
||||
"artifact_chain_hash_v3.json",
|
||||
"artifact_freshness_gate_v1.json",
|
||||
"artifact_freshness_index_v1.json",
|
||||
"artifact_retirement_plan_v1.json",
|
||||
"audit_replay_snapshot_v1.json",
|
||||
"authority_collision_audit_v1.json",
|
||||
"authority_collision_audit_v2.json",
|
||||
"authority_collision_audit_v3.json",
|
||||
"blank_cell_audit_v1.json",
|
||||
"build_bundle_cache_v1.json",
|
||||
"build_bundle_runtime_profile_v1.json",
|
||||
"build_formula_authority_matrix_v1.json",
|
||||
"buy_anti_late_entry_lock_v1.json",
|
||||
"calibration_change_ledger_v1.json",
|
||||
"calibration_change_ledger_v2.json",
|
||||
"calibration_change_ledger_v3.json",
|
||||
"calibration_change_ledger_v4.json",
|
||||
"calibration_priority_v1.json",
|
||||
"calibration_registry_v1.json",
|
||||
"calibration_registry_v2.json",
|
||||
"canonical_artifact_resolver_v1.json",
|
||||
"canonical_artifact_resolver_v1_validation.json",
|
||||
"canonical_metrics_v1.json",
|
||||
"canonical_metrics_v2.json",
|
||||
"canonical_metrics_v3.json",
|
||||
"canonical_metrics_v4.json",
|
||||
"capital_style_allocation_v1.json",
|
||||
"capital_style_allocation_validation_v1.json",
|
||||
"capital_style_time_stop_v1.json",
|
||||
"cash_raise_pareto_executor_v2.json",
|
||||
"cash_raise_pareto_frontier_v1.json",
|
||||
"cash_raise_pareto_frontier_v2.json",
|
||||
"cash_raise_pareto_frontier_v3.json",
|
||||
"cash_raise_value_optimizer_v3.json",
|
||||
"cash_raise_value_preservation_v1.json",
|
||||
"cash_raise_value_preservation_v8.json",
|
||||
"cash_recovery_optimizer_v4.json",
|
||||
"cash_sell_pareto_executor_v1.json",
|
||||
"cashflow_quality_signal_v1.json",
|
||||
"change_request_validation_v1.json",
|
||||
"ci_formula_parity_result_v1.json",
|
||||
"ci_formula_parity_result_v2.json",
|
||||
"compact_bundle_equivalence_v1.json",
|
||||
"completion_gap_v1.json",
|
||||
"computed_harness.json",
|
||||
"confidence_calibration_v2.json",
|
||||
"continuous_evaluation_dashboard_v1.json",
|
||||
"continuous_evaluation_dashboard_v2.json",
|
||||
"continuous_evaluation_dashboard_v3.json",
|
||||
"cross_section_consistency_v1.json",
|
||||
"data_freshness_event_sync_v2.json",
|
||||
"data_freshness_sla_v1.json",
|
||||
"data_integrity_100_lock_v1.json",
|
||||
"data_integrity_100_lock_v2.json",
|
||||
"data_integrity_100_lock_v3.json",
|
||||
"data_integrity_100_lock_v4.json",
|
||||
"data_integrity_100_lock_v5.json",
|
||||
"data_integrity_score_v1.json",
|
||||
"data_maturity_truth_gate_v1.json",
|
||||
"data_quality_gate_v2_py.json",
|
||||
"data_quality_gate_v3.json",
|
||||
"data_quality_reconciliation_v1.json",
|
||||
"data_quality_reconciliation_v2.json",
|
||||
"decision_critical_golden_coverage_v1.json",
|
||||
"decision_evidence_score_v1.json",
|
||||
"decision_evidence_score_v2.json",
|
||||
"decision_replay_snapshot_pack_v1.json",
|
||||
"decision_trace_lock_v1.json",
|
||||
"decision_trace_replay_v1.json",
|
||||
"deprecated_artifact_read_v1.json",
|
||||
"derivation_validity_score_v1.json",
|
||||
"distribution_block_effectiveness_v1.json",
|
||||
"distribution_exit_presignal_v2.json",
|
||||
"distribution_risk_score_v2.json",
|
||||
"distribution_risk_score_v3.json",
|
||||
"distribution_risk_score_v4.json",
|
||||
"docs_rule_duplication_v1.json",
|
||||
"dynamic_value_preservation_sell_v6.json",
|
||||
"earnings_quality_signal_v1.json",
|
||||
"ejce_divergence_audit_v1.json",
|
||||
"ejce_view_renderer_v1.json",
|
||||
"engine_audit_v1.json",
|
||||
"engine_harness_gate_result.json",
|
||||
"engine_harness_gate_result_v2.json",
|
||||
"engine_harness_gate_result_v3.json",
|
||||
"entry_freshness_gate_v2.json",
|
||||
"entry_freshness_gate_v3.json",
|
||||
"entry_freshness_gate_v4.json",
|
||||
"evaluation_history_coverage_v1.json",
|
||||
"execution_authority_matrix_v1.json",
|
||||
"execution_authority_matrix_v2.json",
|
||||
"execution_cost_model_v1.json",
|
||||
"execution_integrity_gate_v1.json",
|
||||
"execution_method_ladder_v1.json",
|
||||
"execution_precedence_lock_v2.json",
|
||||
"execution_quality_harness_v1.json",
|
||||
"execution_readiness_matrix_v1.json",
|
||||
"factor_contract_v1.json",
|
||||
"factor_lifecycle_v1.json",
|
||||
"factor_taxonomy_v1.json",
|
||||
"failure_triage_v1.json",
|
||||
"final_context_for_llm_v1.json",
|
||||
"final_context_for_llm_v1_validation.json",
|
||||
"final_context_for_llm_v2.json",
|
||||
"final_context_for_llm_v3.json",
|
||||
"final_context_for_llm_v4.json",
|
||||
"final_decision_packet_v1.json",
|
||||
"final_decision_packet_v2.json",
|
||||
"final_decision_packet_v3.json",
|
||||
"final_decision_packet_v4.json",
|
||||
"final_execution_decision_v1.json",
|
||||
"final_execution_decision_v2.json",
|
||||
"final_execution_decision_v3.json",
|
||||
"final_execution_decision_v4.json",
|
||||
"final_judgment_gate_v1.json",
|
||||
"formula_behavioral_coverage_summary_v1.json",
|
||||
"formula_behavioral_coverage_v1.json",
|
||||
"formula_behavioral_coverage_v3.json",
|
||||
"formula_compile_report_v1.json",
|
||||
"formula_coverage_authority_v2.json",
|
||||
"formula_coverage_authority_v3.json",
|
||||
"formula_coverage_authority_v4.json",
|
||||
"formula_dependency_graph_v1.json",
|
||||
"formula_gas_parity_v1.json",
|
||||
"formula_owner_coverage_v1.json",
|
||||
"formula_parity_v3.json",
|
||||
"formula_registry_sync_v1.json",
|
||||
"formula_registry_v2_validation.json",
|
||||
"formula_runtime_registry_v1.json",
|
||||
"formula_runtime_registry_v2.json",
|
||||
"formula_version_lifecycle_v1.json",
|
||||
"fundamental_evidence_lock_v1.json",
|
||||
"fundamental_evidence_lock_v2.json",
|
||||
"fundamental_evidence_lock_v3.json",
|
||||
"fundamental_evidence_lock_v4.json",
|
||||
"fundamental_horizon_gate_v2.json",
|
||||
"fundamental_multifactor_v3.json",
|
||||
"fundamental_multifactor_v4.json",
|
||||
"fundamental_raw.json",
|
||||
"fundamental_raw_evidence_v3.json",
|
||||
"fundamental_raw_evidence_v4.json",
|
||||
"fundamental_raw_v1.json",
|
||||
"fundamental_raw_v2.json",
|
||||
"fundamental_source_audit_v1.json",
|
||||
"gas_business_logic_audit_v1.json",
|
||||
"gate_conflict_audit_v1.json",
|
||||
"global_execution_gate_v2.json",
|
||||
"goal_risk_budget_harness_v1.json",
|
||||
"goal_risk_budget_harness_v2.json",
|
||||
"goal_risk_budget_harness_v3.json",
|
||||
"governance_calendar_gate_v1.json",
|
||||
"governance_calendar_gate_v2.json",
|
||||
"governance_calendar_gate_v3.json",
|
||||
"governance_calendar_gate_v4.json",
|
||||
"growth_rate_signal_v1.json",
|
||||
"gs_native_coverage_lock_v1.json",
|
||||
"harness_coverage_audit.json",
|
||||
"harness_gate_result.json",
|
||||
"honest_performance_guard_v1.json",
|
||||
"horizon_allocation_guard_v2.json",
|
||||
"horizon_classification_v1.json",
|
||||
"horizon_rebalance_before_after_v1.json",
|
||||
"horizon_rebalance_before_after_v2.json",
|
||||
"horizon_rebalance_before_after_v3.json",
|
||||
"horizon_rebalance_plan_v1.json",
|
||||
"horizon_router_v1.json",
|
||||
"horizon_routing_lock_v2.json",
|
||||
"horizon_routing_lock_v3.json",
|
||||
"horizon_routing_lock_v4.json",
|
||||
"horizon_routing_lock_v5.json",
|
||||
"horizon_routing_lock_v6.json",
|
||||
"hts_ready_unlock_audit_v1.json",
|
||||
"hts_sell_blueprint_v2.json",
|
||||
"imputed_data_exposure_gate_v2.json",
|
||||
"late_chase_attribution_v1.json",
|
||||
"late_chase_attribution_v2.json",
|
||||
"late_chase_attribution_v3.json",
|
||||
"late_chase_attribution_v4.json",
|
||||
"late_rebound_bucket_score_v1.json",
|
||||
"liquidity_execution_quality_v1.json",
|
||||
"liquidity_execution_quality_v2.json",
|
||||
"liquidity_flow_signal_v1.json",
|
||||
"live_replay_separation_v2.json",
|
||||
"live_trade_outcome_ledger_v1.json",
|
||||
"live_vs_replay_performance_audit_v1.json",
|
||||
"live_vs_replay_performance_audit_v2.json",
|
||||
"llm_freedom_v1.json",
|
||||
"llm_narrative_template_lock_v1.json",
|
||||
"llm_narrative_template_lock_v2.json",
|
||||
"llm_render_contract_v2.json",
|
||||
"llm_response_validation_v1.json",
|
||||
"llm_response_validation_v2.json",
|
||||
"llm_response_validation_v3.json",
|
||||
"llm_response_validation_v4.json",
|
||||
"low_capability_response_contract_runtime_v1.json",
|
||||
"low_n_pass_gate_v1.json",
|
||||
"macro_event_synchronizer_v2.json",
|
||||
"macro_event_synchronizer_v3.json",
|
||||
"macro_event_ticker_impact_v1.json",
|
||||
"market_share_signal_v2.json",
|
||||
"metric_definition_registry_v2.json",
|
||||
"missing_field_patch_plan_v1.json",
|
||||
"missing_field_patch_plan_v2.json",
|
||||
"missing_field_patch_plan_v3.json",
|
||||
"no_temp_runtime_read_v1.json",
|
||||
"number_provenance_audit_v1.json",
|
||||
"number_provenance_audit_v2.json",
|
||||
"number_provenance_audit_v3.json",
|
||||
"number_provenance_ledger_v4.json",
|
||||
"number_provenance_strict_v2.json",
|
||||
"number_provenance_strict_v3.json",
|
||||
"operational_alpha_calibration_v2.json",
|
||||
"operational_eval_queue_v1.json",
|
||||
"operational_evidence_audit_v1.json",
|
||||
"operational_outcome_lock_v1.json",
|
||||
"operational_outcome_lock_v2.json",
|
||||
"operational_outcome_lock_v3.json",
|
||||
"operational_outcome_lock_v4.json",
|
||||
"operational_t20_outcome_ledger_v1.json",
|
||||
"operational_truth_score_v1.json",
|
||||
"order_blueprint_consistency_v1.json",
|
||||
"order_blueprint_consistency_v2.json",
|
||||
"order_blueprint_v2.json",
|
||||
"order_blueprint_v3.json",
|
||||
"order_form_strict_lock_v2.json",
|
||||
"order_math_reconciliation_v1.json",
|
||||
"order_math_reconciliation_v2.json",
|
||||
"order_math_reconciliation_v3.json",
|
||||
"order_math_reconciliation_v4.json",
|
||||
"outcome_ledger_v1.json",
|
||||
"outcome_ledger_v2.json",
|
||||
"outcome_quality_score_v1.json",
|
||||
"output_field_owner_collision_v1.json",
|
||||
"pass_100_authority_lock_v1.json",
|
||||
"pass_100_criteria_v1.json",
|
||||
"pass_100_criteria_v2.json",
|
||||
"pass_100_criteria_v3.json",
|
||||
"pass_100_criteria_v4.json",
|
||||
"pass_100_honest_v1.json",
|
||||
"perf_recovery_harness_v1.json",
|
||||
"performance_monitoring_dashboard_v1.json",
|
||||
"performance_readiness_replay_bridge_v1.json",
|
||||
"performance_readiness_replay_bridge_v2.json",
|
||||
"performance_readiness_unlock_v1.json",
|
||||
"phase_checks_50_60.json",
|
||||
"pipeline_runtime_profile_history_v1.json",
|
||||
"pipeline_runtime_profile_v1.json",
|
||||
"portfolio_alpha_confidence_per_ticker_v1.json",
|
||||
"pre_distribution_early_warning_v3.json",
|
||||
"prediction_accuracy_harness_v2.json",
|
||||
"prediction_accuracy_harness_v3.json",
|
||||
"prediction_accuracy_harness_v4.json",
|
||||
"prediction_accuracy_harness_v5.json",
|
||||
"prediction_improvement_harness.json",
|
||||
"prediction_lift_dashboard_v1.json",
|
||||
"predictive_alpha_calibration_v2.json",
|
||||
"predictive_alpha_calibration_v3.json",
|
||||
"predictive_alpha_engine_v2.json",
|
||||
"predictive_alpha_report_lock_v2.json",
|
||||
"prompt_formula_leak_audit_v1.json",
|
||||
"proposal_evaluation_history.json",
|
||||
"quant_engine_hardening_todo_schema_v1.json",
|
||||
"quant_engine_hardening_todo_v1.json",
|
||||
"ratchet_trailing_general_v1.json",
|
||||
"realized_performance_v1.json",
|
||||
"rebound_sell_calibration_v2.json",
|
||||
"rebound_sell_efficiency_v1.json",
|
||||
"rebound_wait_order_plan_v1.json",
|
||||
"rebound_wait_order_plan_v2.json",
|
||||
"refactor_baseline_inventory_v1.json",
|
||||
"refactor_baseline_metrics_v1.json",
|
||||
"relative_underperformance_alert_v1.json",
|
||||
"release_ci_gate_v2.json",
|
||||
"release_dag_v1.json",
|
||||
"release_gate_sequence_v1.json",
|
||||
"release_gate_summary_v1.json",
|
||||
"release_gate_summary_v2.json",
|
||||
"release_gate_summary_v3.json",
|
||||
"renderer_no_calculation_v1.json",
|
||||
"replay_live_separation_v1.json",
|
||||
"repo_hygiene_report.json",
|
||||
"report_authority_diff_v1.json",
|
||||
"report_authority_diff_v2.json",
|
||||
"report_order_lock_v1.json",
|
||||
"report_order_lock_v2.json",
|
||||
"report_render_integrity_v1.json",
|
||||
"request_result_adoption_v1.json",
|
||||
"root_cause_attribution_v1.json",
|
||||
"root_cause_recovery_plan_v1.json",
|
||||
"routing_execution_log_table_v1.json",
|
||||
"routing_execution_log_v1.json",
|
||||
"routing_serving_authority_v1.json",
|
||||
"routing_serving_trace_v2.json",
|
||||
"routing_serving_trace_v3.json",
|
||||
"rule_lifecycle_governance_v4.json",
|
||||
"rule_lifecycle_policy.json",
|
||||
"rule_lifecycle_policy_v2.json",
|
||||
"rule_lifecycle_policy_v3.json",
|
||||
"rule_lifecycle_strict_v1.json",
|
||||
"schema_model_generation_v1.json",
|
||||
"scores_harness_v1.json",
|
||||
"sell_engine_audit_v1.json",
|
||||
"sell_execution_timing_lock_v2.json",
|
||||
"sell_price_sanity_v2.json",
|
||||
"sell_price_sanity_v3.json",
|
||||
"sell_waterfall_engine_v2.json",
|
||||
"sell_waterfall_engine_v3.json",
|
||||
"semantic_formula_coverage_v1.json",
|
||||
"shadow_ledger_v1.json",
|
||||
"short_horizon_outcome_monitor_v1.json",
|
||||
"single_truth_ledger_v1.json",
|
||||
"single_truth_ledger_v2.json",
|
||||
"single_truth_ledger_v3.json",
|
||||
"smart_cash_recovery_v3.json",
|
||||
"smart_cash_recovery_v4.json",
|
||||
"smart_cash_recovery_v5.json",
|
||||
"smart_cash_recovery_v6.json",
|
||||
"smart_cash_recovery_v7.json",
|
||||
"smart_cash_recovery_v7_authoritative.json",
|
||||
"smart_cash_recovery_v8.json",
|
||||
"smart_cash_recovery_v9.json",
|
||||
"smart_money_flow_signal_v2.json",
|
||||
"smart_money_liquidity_composite_v2.json",
|
||||
"smart_money_liquidity_composite_v3.json",
|
||||
"smart_money_liquidity_evidence_gate_v2.json",
|
||||
"smart_money_liquidity_evidence_gate_v3.json",
|
||||
"smart_money_liquidity_evidence_gate_v4.json",
|
||||
"smart_money_liquidity_evidence_gate_v5.json",
|
||||
"smart_money_liquidity_gate_v1.json",
|
||||
"smart_money_liquidity_gate_v2.json",
|
||||
"smart_money_liquidity_microstructure_v4.json",
|
||||
"smart_money_liquidity_outcome_link_v1.json",
|
||||
"source_authority_collapse_v1.json",
|
||||
"source_freshness_manifest_v1.json",
|
||||
"source_freshness_manifest_v2.json",
|
||||
"strategy_decision_result_v3.json",
|
||||
"strategy_execution_locks_regression_result.json",
|
||||
"strategy_hardening_harness_v1.json",
|
||||
"strategy_hardening_harness_v2.json",
|
||||
"strategy_harness_score.json",
|
||||
"strategy_harness_v2.json",
|
||||
"strategy_release_stage_v1.json",
|
||||
"strategy_routing_audit_v1.json",
|
||||
"tail_risk_guard_v1.json",
|
||||
"tail_risk_guard_v2.json",
|
||||
"tool_thin_wrapper_v1.json",
|
||||
"trade_quality_from_t5_v1.json",
|
||||
"truth_reconciliation_gate_v1.json",
|
||||
"truthful_decision_ledger_v2.json",
|
||||
"truthful_decision_ledger_v3.json",
|
||||
"truthful_decision_ledger_v4.json",
|
||||
"truthfulness_guard_v1.json",
|
||||
"twap_child_order_blueprint_v1.json",
|
||||
"twap_child_order_blueprint_v2.json",
|
||||
"unified_route_packet_v1.json",
|
||||
"vacuous_pass_audit_v1.json",
|
||||
"value_preservation_scorer_v1.json",
|
||||
"value_preservation_scorer_v2.json",
|
||||
"value_preserving_cash_raise_optimizer_v9.json",
|
||||
"value_preserving_cash_raise_v1.json",
|
||||
"verdict_consistency_lock_v1.json",
|
||||
"walk_forward_calibration_v1.json",
|
||||
"walk_forward_performance_v1.json",
|
||||
"walk_forward_performance_v2.json",
|
||||
"yaml_code_coverage_full.json",
|
||||
"yaml_code_coverage_v1.json",
|
||||
"yaml_gs_ps_coverage.json",
|
||||
"yaml_gs_ps_coverage_v2.json",
|
||||
"yaml_gs_py_xref_matrix_v2.json"
|
||||
],
|
||||
"candidate_count": 388,
|
||||
"archived_count": 0,
|
||||
"gate": "PASS"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"formula_id": "ALPHA_LEAD_THRESHOLD_OPTIMIZER_V3",
|
||||
"gate": "PASS",
|
||||
"prediction_match_rate_pct": 54.76,
|
||||
"t5_direction_accuracy_pct": 54.76,
|
||||
"late_chase_false_positive_rate": 20.0,
|
||||
"buy_after_5d_runup_without_pullback_count": 0,
|
||||
"threshold_ledger": [
|
||||
{
|
||||
"threshold": 75,
|
||||
"pullback_quality": 60,
|
||||
"distribution_score": 2.0,
|
||||
"action": "PILOT_ALLOWED"
|
||||
},
|
||||
{
|
||||
"threshold": 65,
|
||||
"pullback_quality": 50,
|
||||
"distribution_score": 1.5,
|
||||
"action": "CANDIDATE_ONLY"
|
||||
}
|
||||
],
|
||||
"generated_at": "2026-06-03T09:08:54.180836+00:00"
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"formula_id": "ANTI_LATE_ENTRY_PULLBACK_GATE_V4",
|
||||
"gate": "WATCH",
|
||||
"late_chase_buy_violations": 0,
|
||||
"late_chase_false_positive_rate": 33.65,
|
||||
"buy_after_5d_runup_without_pullback_count": 0,
|
||||
"pullback_quality_required_for_buy": 60,
|
||||
"distribution_score_for_buy": 1.5,
|
||||
"prediction_match_rate_pct": 54.76,
|
||||
"t5_direction_accuracy_pct": 54.76,
|
||||
"late_chase_operational_samples": 263,
|
||||
"late_chase_gate_hit_miss_rate_published": true,
|
||||
"threshold_ledger": [
|
||||
{
|
||||
"threshold": 75,
|
||||
"pullback_quality": 60,
|
||||
"distribution_score": 2.0,
|
||||
"action": "PILOT_ALLOWED"
|
||||
},
|
||||
{
|
||||
"threshold": 65,
|
||||
"pullback_quality": 50,
|
||||
"distribution_score": 1.5,
|
||||
"action": "CANDIDATE_ONLY"
|
||||
}
|
||||
],
|
||||
"supporting_artifacts": [
|
||||
"Temp/alpha_lead_threshold_optimizer_v3.json",
|
||||
"Temp/buy_anti_late_entry_lock_v1.json",
|
||||
"Temp/late_chase_attribution_v1.json"
|
||||
],
|
||||
"generated_at": "2026-06-03T15:58:27.382232+00:00"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"formula_id": "CANONICAL_METRICS_V4",
|
||||
"generated_at": "2026-05-31T23:23:43+09:00",
|
||||
"builder_version": "v5.todo.batch",
|
||||
"canonical_metrics": [
|
||||
{
|
||||
"metric_id": "global_execution_gate",
|
||||
"canonical_json_path": "Temp/final_execution_decision_v3.json",
|
||||
"value": "AUDIT_ONLY"
|
||||
},
|
||||
{
|
||||
"metric_id": "schema_presence_score",
|
||||
"canonical_json_path": "Temp/data_integrity_100_lock_v5.json",
|
||||
"value": 95.5
|
||||
},
|
||||
{
|
||||
"metric_id": "pass_100_gate",
|
||||
"canonical_json_path": "Temp/pass_100_criteria_v1.json",
|
||||
"value": "BLOCK_EXECUTION"
|
||||
}
|
||||
],
|
||||
"authority_collision_count": 0,
|
||||
"stale_artifact_count": 0
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"formula_id": "DISTRIBUTION_RISK_SCORE_V4",
|
||||
"generated_at": "2026-05-31T23:23:43+09:00",
|
||||
"builder_version": "v5.todo.batch",
|
||||
"distribution_risk_coverage_pct": 100.0,
|
||||
"false_chase_entry_rate_max_pct": 20.0,
|
||||
"high_risk_count": 0
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V4",
|
||||
"global_execution_gate": "AUDIT_ONLY",
|
||||
"buy_allowed": false,
|
||||
"sell_allowed": false,
|
||||
"hts_order_count": 0,
|
||||
"child_engine_internal_allowed": true,
|
||||
"child_execution_state": "THEORETICAL_ONLY",
|
||||
"precedence_note": "child_engine_internal_allowed=True는 현금회복 계산이 내부적으로 허용됨을 의미한다. HTS 주문 실행은 global_execution_gate==HTS_READY일 때만 허용된다.",
|
||||
"reason_codes": [
|
||||
"TRUTH_GATE=BLOCK_EXECUTION",
|
||||
"TRUTH_SCORE=70.00",
|
||||
"READINESS_GATE=WATCH_PENDING_SAMPLE",
|
||||
"EXECUTION_READINESS_MATRIX=BLOCK_EXECUTION:0.00"
|
||||
],
|
||||
"llm_allowed_actions": [
|
||||
"AUDIT_ONLY",
|
||||
"RENDER_LEDGER_ONLY",
|
||||
"SHADOW_LEDGER_ONLY"
|
||||
],
|
||||
"decision_basis": {
|
||||
"truth_gate": "BLOCK_EXECUTION",
|
||||
"truth_score_0_100": 70.0,
|
||||
"final_judgment_gate": "PASS",
|
||||
"final_judgment_coverage_pct": 100.0,
|
||||
"smart_cash_recovery_status": "PASS",
|
||||
"smart_cash_recovery_child_engine_internal_allowed": true,
|
||||
"smart_cash_recovery_value_damage_pct": 0.0,
|
||||
"readiness_gate": "WATCH_PENDING_SAMPLE",
|
||||
"execution_readiness_matrix_gate": "BLOCK_EXECUTION",
|
||||
"execution_readiness_min_axis_score": 0.0,
|
||||
"hts_candidate_rows": 0
|
||||
},
|
||||
"generated_at": "2026-06-06T11:52:58.416423+00:00",
|
||||
"input_hash": "8af12b88c7f8b3d3803cf447d309394aa83ddd3576f9427282992cb9e9e757d9"
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"formula_id": "PASS_100_CRITERIA_V3",
|
||||
"is_active": true,
|
||||
"authority_note": "유일한 active PASS_100 기준. v1/v2는 legacy_reference_only.",
|
||||
"legacy_files": [
|
||||
"pass_100_criteria_v2.json"
|
||||
],
|
||||
"gate": "BLOCK_EXECUTION",
|
||||
"pass_100_allowed": false,
|
||||
"score_0_100": 46.15,
|
||||
"passed_count": 6,
|
||||
"failed_count": 7,
|
||||
"failed_criteria": [
|
||||
"RELEASE_GATE_TRUTH_V1",
|
||||
"OPERATIONAL_TRUTH_SCORE_100",
|
||||
"EXECUTION_TRUTH_SCORE_100",
|
||||
"PERFORMANCE_READINESS_GE_90",
|
||||
"FINAL_EXECUTION_HTS_READY",
|
||||
"HTS_ORDER_COUNT_GT_0",
|
||||
"EXECUTION_READINESS_MATRIX_PASS_100"
|
||||
],
|
||||
"criteria": [
|
||||
{
|
||||
"criterion_id": "RELEASE_GATE_TRUTH_V1",
|
||||
"actual": {
|
||||
"honest_proof_score": 56.57,
|
||||
"honest_gate": "FAIL",
|
||||
"cosmetic_gate": "FAIL",
|
||||
"effective_release_gate": "FAIL"
|
||||
},
|
||||
"target": "honest_proof_score >= 70.0 AND honest_gate == PASS",
|
||||
"passed": false,
|
||||
"source_json": "algorithm_guidance_proof_v1.json",
|
||||
"formula_id": "RELEASE_GATE_TRUTH_V1",
|
||||
"remediation": "honest_proof_score < 70 → T+20 표본 30건 축적 후 자동 해소 (~2026-07)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SCHEMA_PRESENCE_100",
|
||||
"actual": 100.0,
|
||||
"target": "== 100",
|
||||
"passed": true,
|
||||
"source_json": "data_quality_reconciliation_v1.json",
|
||||
"formula_id": "DATA_QUALITY_RECONCILIATION_V1",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "FORMULA_RUNTIME_COVERAGE_100",
|
||||
"actual": 100.0,
|
||||
"target": "== 100",
|
||||
"passed": true,
|
||||
"source_json": "strategy_hardening_harness_v2.json",
|
||||
"formula_id": "STRATEGY_HARDENING_HARNESS_V2",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "CONFIDENCE_CAP_BASIS_GE_90",
|
||||
"actual": 100.0,
|
||||
"target": ">= 90",
|
||||
"passed": true,
|
||||
"source_json": "data_quality_reconciliation_v1.json",
|
||||
"formula_id": "DATA_QUALITY_RECONCILIATION_V1",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SINGLE_TRUTH_NO_CONFLICT",
|
||||
"actual": 0,
|
||||
"target": "== 0",
|
||||
"passed": true,
|
||||
"source_json": "single_truth_ledger_v2.json",
|
||||
"formula_id": "SINGLE_TRUTH_LEDGER_V2",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "OPERATIONAL_TRUTH_SCORE_100",
|
||||
"actual": 70.0,
|
||||
"target": "== 100 and gate PASS_100",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "export_gate 해소(HTS 캡처) + performance_readiness 해소 필요",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "EXECUTION_TRUTH_SCORE_100",
|
||||
"actual": 0.0,
|
||||
"target": "== 100",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "HTS 캡처 → export_gate PASS → execution_truth 자동 해소",
|
||||
"remediation_type": "OPERATIONAL_ACTION"
|
||||
},
|
||||
{
|
||||
"criterion_id": "PERFORMANCE_READINESS_GE_90",
|
||||
"actual": 50.0,
|
||||
"target": ">= 90 and readiness_gate PERFORMANCE_READY",
|
||||
"passed": false,
|
||||
"source_json": "operational_truth_score_v1.json",
|
||||
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
|
||||
"remediation": "T+20 운영 표본 30건 필요 — 매일 자동 축적 (~2026-07-05~07-15)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SMART_CASH_VALUE_DAMAGE_LE_10",
|
||||
"actual": {
|
||||
"value_damage_adj": 0.0,
|
||||
"value_damage_raw": 0.0,
|
||||
"source": "smart_cash_recovery_v7.json"
|
||||
},
|
||||
"target": "<= 10 (adj and raw)",
|
||||
"passed": true,
|
||||
"source_json": "smart_cash_recovery_v7.json",
|
||||
"formula_id": "SMART_CASH_RECOVERY_V7",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "SMART_CASH_EXECUTION_ALLOWED",
|
||||
"actual": true,
|
||||
"target": "is true and status PASS",
|
||||
"passed": true,
|
||||
"source_json": "smart_cash_recovery_v7.json",
|
||||
"formula_id": "SMART_CASH_RECOVERY_V7",
|
||||
"remediation": "NONE",
|
||||
"remediation_type": "NONE"
|
||||
},
|
||||
{
|
||||
"criterion_id": "FINAL_EXECUTION_HTS_READY",
|
||||
"actual": "AUDIT_ONLY",
|
||||
"target": "HTS_READY",
|
||||
"passed": false,
|
||||
"source_json": "final_execution_decision_v2.json",
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"remediation": "PERFORMANCE_READINESS_GE_90 해소 후 자동 연쇄 해소 (~2026-07-15)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "HTS_ORDER_COUNT_GT_0",
|
||||
"actual": 0,
|
||||
"target": "> 0",
|
||||
"passed": false,
|
||||
"source_json": "final_execution_decision_v2.json",
|
||||
"formula_id": "FINAL_EXECUTION_DECISION_V2",
|
||||
"remediation": "FINAL_EXECUTION_HTS_READY 해소 후 자동 연쇄 해소 (~2026-07-15)",
|
||||
"remediation_type": "DATA_GATED",
|
||||
"data_gated_until": "2026-07-15"
|
||||
},
|
||||
{
|
||||
"criterion_id": "EXECUTION_READINESS_MATRIX_PASS_100",
|
||||
"actual": {
|
||||
"gate": "BLOCK_EXECUTION",
|
||||
"min_axis_score": 0.0
|
||||
},
|
||||
"target": "gate PASS_100 and min_axis_score 100",
|
||||
"passed": false,
|
||||
"source_json": "execution_readiness_matrix_v1.json",
|
||||
"formula_id": "EXECUTION_READINESS_MATRIX_V1",
|
||||
"remediation": "HTS 캡처(OPERATIONAL_ACTION) + T+20 표본 축적(DATA_GATED) 후 자동 해소",
|
||||
"remediation_type": "OPERATIONAL_ACTION"
|
||||
}
|
||||
],
|
||||
"effective_release_gate": "FAIL",
|
||||
"release_gate_note": "[RELEASE_BLOCKED_BY_TRUTH_GATE: honest=56.57 < 70]",
|
||||
"hts_order_mode": "THEORETICAL_ONLY",
|
||||
"targets": {
|
||||
"completion_definition": "PASS_100 only when every criterion passed",
|
||||
"hts_execution_definition": "HTS execution forbidden unless FINAL_EXECUTION_HTS_READY and HTS_ORDER_COUNT_GT_0 pass",
|
||||
"llm_role": "copy criteria and render ledgers only; no ad-hoc numeric overrides",
|
||||
"release_gate_truth_definition": "effective_release_gate = AND(cosmetic_gate, honest_gate); honest_proof_score < 70 → THEORETICAL_ONLY"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"formula_id": "PREDICTION_ACCURACY_HARNESS_V5",
|
||||
"generated_at": "2026-06-03T00:00:00+09:00",
|
||||
"builder_version": "v5.todo.batch.p0-3",
|
||||
"t20_operational_sample": 0,
|
||||
"t20_operational_accuracy_pct": null,
|
||||
"performance_readiness_score": 50.0,
|
||||
"prediction_match_rate_pct": 47.28,
|
||||
"operational_t5_sample_count": 0,
|
||||
"operational_t20_sample_count": 0,
|
||||
"data_origin_audit": {
|
||||
"operational_sample_count": 0,
|
||||
"replay_sample_count": 804,
|
||||
"untagged_row_count": 0,
|
||||
"unrealized_outcome_row_count": 0,
|
||||
"replay_in_live_stats": 0,
|
||||
"operational_only_accuracy": true,
|
||||
"untagged_label": "INSUFFICIENT_OP_SAMPLES(n=0)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"formula_id": "VALUE_PRESERVING_CASH_RAISE_V9",
|
||||
"generated_at": "2026-06-03T13:32:06.415328+00:00",
|
||||
"cash_shortfall_min_krw": 36092555.0,
|
||||
"raw_value_damage_pct_avg": 15.7,
|
||||
"adjusted_value_damage_pct_avg": 0.0,
|
||||
"value_damage_gate_input": 15.7,
|
||||
"rebound_capture_probability": 0.0,
|
||||
"selected_sell_combo": [
|
||||
{
|
||||
"ticker": "064350",
|
||||
"source": "BREACH_FULL_LIQUIDATION",
|
||||
"expected_immediate_krw": 57841575
|
||||
}
|
||||
],
|
||||
"liquidation_policy_check": {
|
||||
"breach_full_liquidation_count": 1,
|
||||
"breach_violations": [
|
||||
{
|
||||
"ticker": "064350",
|
||||
"issue": "LP002_BREACH_FULL_LIQUIDATION_BAN",
|
||||
"note": "oversold 또는 brt_verdict!=BROKEN — K2 50/50 적용 필요"
|
||||
}
|
||||
],
|
||||
"k2_split_applied": [],
|
||||
"single_stock_max_concentration_pct": 100.0,
|
||||
"value_damage_cap_pass": false,
|
||||
"rebound_capture_probability": 0.0,
|
||||
"lp004_blocked": true,
|
||||
"lp002_blocked": true
|
||||
},
|
||||
"gate": "FAIL",
|
||||
"gate_failures": [
|
||||
"LP004_VALUE_DAMAGE_CAP_EXCEEDED",
|
||||
"LP002_BREACH_FULL_LIQUIDATION_DETECTED"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"formula_id": "SMART_MONEY_LIQUIDITY_EVIDENCE_GATE_V5",
|
||||
"generated_at": "2026-05-31T23:23:43+09:00",
|
||||
"builder_version": "v5.todo.batch",
|
||||
"gate": "OK",
|
||||
"ticker_count": 11,
|
||||
"freshness_sla_breaches": 0
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
schema_version: 2026-06-06-canonical-manifest-v1
|
||||
source_spec: spec/32_canonical_artifact_resolver.yaml
|
||||
generated_from: Temp/canonical_artifact_resolver_v1.json
|
||||
|
||||
concepts:
|
||||
smart_cash_recovery:
|
||||
canonical_path: artifacts/canonical/smart_cash_recovery_v9.json
|
||||
source_file: Temp/smart_cash_recovery_v9.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/smart_cash_recovery_v8.json
|
||||
- artifacts/archive/2026-06-06/smart_cash_recovery_v7.json
|
||||
- artifacts/archive/2026-06-06/smart_cash_recovery_v6.json
|
||||
- artifacts/archive/2026-06-06/smart_cash_recovery_v5.json
|
||||
- artifacts/archive/2026-06-06/smart_cash_recovery_v4.json
|
||||
- artifacts/archive/2026-06-06/smart_cash_recovery_v3.json
|
||||
distribution_risk_score:
|
||||
canonical_path: artifacts/canonical/distribution_risk_score_v4.json
|
||||
source_file: Temp/distribution_risk_score_v4.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/distribution_risk_score_v3.json
|
||||
- artifacts/archive/2026-06-06/distribution_risk_score_v2.json
|
||||
final_execution_decision:
|
||||
canonical_path: artifacts/canonical/final_execution_decision_v4.json
|
||||
source_file: Temp/final_execution_decision_v4.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/final_execution_decision_v3.json
|
||||
- artifacts/archive/2026-06-06/final_execution_decision_v2.json
|
||||
- artifacts/archive/2026-06-06/final_execution_decision_v1.json
|
||||
alpha_lead_threshold_optimizer:
|
||||
canonical_path: artifacts/canonical/alpha_lead_threshold_optimizer_v3.json
|
||||
source_file: Temp/alpha_lead_threshold_optimizer_v3.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/alpha_lead_threshold_optimizer_v2.json
|
||||
- artifacts/archive/2026-06-06/alpha_lead_threshold_optimizer_v1.json
|
||||
pass_100_criteria:
|
||||
canonical_path: artifacts/canonical/pass_100_criteria_v3.json
|
||||
source_file: Temp/pass_100_criteria_v3.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/pass_100_criteria_v2.json
|
||||
- artifacts/archive/2026-06-06/pass_100_criteria_v1.json
|
||||
prediction_accuracy_harness:
|
||||
canonical_path: artifacts/canonical/prediction_accuracy_harness_v5.json
|
||||
source_file: Temp/prediction_accuracy_harness_v5.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/prediction_accuracy_harness_v4.json
|
||||
- artifacts/archive/2026-06-06/prediction_accuracy_harness_v3.json
|
||||
- artifacts/archive/2026-06-06/prediction_accuracy_harness_v2.json
|
||||
smart_money_liquidity_evidence_gate:
|
||||
canonical_path: artifacts/canonical/smart_money_liquidity_evidence_gate_v5.json
|
||||
source_file: Temp/smart_money_liquidity_evidence_gate_v5.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/smart_money_liquidity_evidence_gate_v4.json
|
||||
- artifacts/archive/2026-06-06/smart_money_liquidity_evidence_gate_v3.json
|
||||
- artifacts/archive/2026-06-06/smart_money_liquidity_evidence_gate_v2.json
|
||||
canonical_metrics:
|
||||
canonical_path: artifacts/canonical/canonical_metrics_v4.json
|
||||
source_file: Temp/canonical_metrics_v4.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/canonical_metrics_v3.json
|
||||
- artifacts/archive/2026-06-06/canonical_metrics_v2.json
|
||||
- artifacts/archive/2026-06-06/canonical_metrics_v1.json
|
||||
anti_late_entry_pullback_gate:
|
||||
canonical_path: artifacts/canonical/anti_late_entry_pullback_gate_v4.json
|
||||
source_file: Temp/anti_late_entry_pullback_gate_v4.json
|
||||
deprecated_files:
|
||||
- artifacts/archive/2026-06-06/anti_late_entry_pullback_gate_v3.json
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR-0001 Single Source of Truth
|
||||
|
||||
## Context
|
||||
|
||||
The engine has multiple JSON versions for the same concept, which creates stale reads and conflicting runtime authority.
|
||||
|
||||
## Decision
|
||||
|
||||
Use `canonical_manifest.yaml` and the canonical artifact resolver as the runtime authority for each concept.
|
||||
|
||||
## Consequence
|
||||
|
||||
Runtime readers must use one canonical path per concept. Deprecated files remain archived only.
|
||||
|
||||
## Rollback
|
||||
|
||||
If a canonical artifact is invalid, rebuild the canonical copy rather than falling back to a deprecated runtime source.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR-0002 GAS Thin Adapter
|
||||
|
||||
## Context
|
||||
|
||||
GAS has accumulated business logic that belongs in Python validation and builders.
|
||||
|
||||
## Decision
|
||||
|
||||
Constrain GAS to collection, normalization, export, and display responsibilities.
|
||||
|
||||
## Consequence
|
||||
|
||||
Decision, sizing, stop-loss, take-profit, and risk calculations must live in deterministic Python builders.
|
||||
|
||||
## Rollback
|
||||
|
||||
If a GAS function becomes a business-logic dependency, move the logic into Python and keep only a thin adapter in GAS.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR-0003 No LLM Numeric Generation
|
||||
|
||||
## Context
|
||||
|
||||
LLM-generated prices, quantities, and thresholds caused authority collisions and provenance loss.
|
||||
|
||||
## Decision
|
||||
|
||||
LLM outputs must be copy-only. All numeric values must come from harness or canonical artifacts.
|
||||
|
||||
## Consequence
|
||||
|
||||
Prompt and report contracts must render numbers only when a source artifact already provides them.
|
||||
|
||||
## Rollback
|
||||
|
||||
There is no rollback path that reintroduces free-form numeric generation. That would violate the operating contract.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR-0004 Shadow Before Active
|
||||
|
||||
## Context
|
||||
|
||||
Replay and shadow artifacts often look complete before live T+20 sample quality is sufficient.
|
||||
|
||||
## Decision
|
||||
|
||||
Shadow or advisory formulas may be rendered, but only active formulas may drive live execution.
|
||||
|
||||
## Consequence
|
||||
|
||||
Live sample thresholds remain mandatory before promotion.
|
||||
|
||||
## Rollback
|
||||
|
||||
If active promotion is found without live qualification, demote the artifact and restore shadow-only status.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
meta:
|
||||
source: AGENTS.md
|
||||
version: 1
|
||||
purpose: Extract top-level rules into canonical spec targets without changing AGENTS.md.
|
||||
|
||||
principles:
|
||||
- rule_key: no_llm_numeric_generation
|
||||
target_spec_path: spec/00_execution_contract.yaml
|
||||
- rule_key: single_formula_registry_source
|
||||
target_spec_path: spec/13_formula_registry.yaml
|
||||
- rule_key: runtime_temp_direct_read_block
|
||||
target_spec_path: spec/32_canonical_artifact_resolver.yaml
|
||||
- rule_key: replay_live_separation
|
||||
target_spec_path: spec/37_evaluation_dashboard_contract.yaml
|
||||
- rule_key: live_t20_promotion_lock
|
||||
target_spec_path: spec/35_rule_lifecycle_governance_v3.yaml
|
||||
- rule_key: validation_failure_no_bypass
|
||||
target_spec_path: spec/21_harness_governance_contract.yaml
|
||||
- rule_key: data_missing_only
|
||||
target_spec_path: spec/07_output_schema.yaml
|
||||
- rule_key: gas_thin_adapter
|
||||
target_spec_path: spec/34_architecture_boundaries.yaml
|
||||
- rule_key: prompt_numeric_freeze
|
||||
target_spec_path: spec/31_low_capability_llm_response_contract.yaml
|
||||
- rule_key: single_execution_authority
|
||||
target_spec_path: spec/33_execution_precedence_lock.yaml
|
||||
- rule_key: hts_order_executable_only_after_gate
|
||||
target_spec_path: spec/19_harness_contract.yaml
|
||||
- rule_key: no_intraday_aggressive_buy_before_close
|
||||
target_spec_path: spec/06_exit_policy.yaml
|
||||
|
||||
mapping_policy:
|
||||
- "The original AGENTS.md is not edited by this proposal."
|
||||
- "Each extracted rule must have a target_spec_path."
|
||||
- "The principles list is intentionally capped at 12."
|
||||
@@ -0,0 +1,13 @@
|
||||
# Doctrine
|
||||
|
||||
This repository is a deterministic quant engine.
|
||||
|
||||
## Operating Rules
|
||||
|
||||
- Numbers are copied from canonical artifacts, not invented in the renderer.
|
||||
- `Temp/` is a build/output layer, not a runtime authority.
|
||||
- Replay metrics are informational unless explicitly labeled live and sample-qualified.
|
||||
- Live T+20 sample counts below 30 cannot unlock `active` or `PASS_100`.
|
||||
- GAS is an adapter layer, not a business-logic layer.
|
||||
- Prompts are copy-only renderers and must not compute prices, quantities, or thresholds.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# AGENTS Constitution Proposal
|
||||
|
||||
This proposal extracts the top-level operating principles from `AGENTS.md` without changing the original file.
|
||||
|
||||
## Principles
|
||||
|
||||
1. No price, quantity, stop, take-profit, or score may be invented by the LLM.
|
||||
2. Only registered formula IDs in `spec/13_formula_registry.yaml` may be cited.
|
||||
3. `Temp/` files may not be used as runtime source unless explicitly designated as build output.
|
||||
4. Canonical artifacts must be read through `canonical_manifest.yaml`.
|
||||
5. Replay performance must never be presented as live performance.
|
||||
6. Live T+20 sample counts below 30 may not be promoted to `active` or `PASS_100`.
|
||||
7. Validation failures may not be bypassed with narrative explanations.
|
||||
8. Missing files or missing provenance must be rendered as `DATA_MISSING`.
|
||||
9. GAS may not gain new investment decision logic.
|
||||
10. Prompts may not request the LLM to calculate prices, quantities, thresholds, or scores.
|
||||
11. Failed harness states may not be rendered as executable order tables.
|
||||
12. Final decision authority must come from a single canonical execution packet.
|
||||
|
||||
## Notes
|
||||
|
||||
- This document is a proposal only.
|
||||
- The original `AGENTS.md` remains unchanged.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Runbook
|
||||
|
||||
1. Build or refresh the JSON harness with `npm run prepare-json`.
|
||||
2. Run strict validation gates before packaging.
|
||||
3. Generate canonical artifacts through the builder scripts only.
|
||||
4. Render reports from canonical data only.
|
||||
5. Package upload artifacts only after the full gate passes or the output is explicitly audit-only.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
case_id: EX_AVOID_001
|
||||
type: AVOID
|
||||
input_summary:
|
||||
flow_ok: "N"
|
||||
atr20_status: "DATA_MISSING"
|
||||
total_score: 88
|
||||
expected_output:
|
||||
investment_action: "AVOID"
|
||||
grade: "D"
|
||||
reason: "Risk/data hard filters override high score."
|
||||
triggered_rules:
|
||||
- "Flow_OK=N이면 신규매수 금지"
|
||||
- "ATR20 미확인 시 정수 매수수량 산출 금지"
|
||||
@@ -0,0 +1,18 @@
|
||||
case_id: EX_BUY_001
|
||||
type: BUY
|
||||
input_summary:
|
||||
data_status: "all_required_fields_ok"
|
||||
hard_filters: "pass"
|
||||
flow_rows: 20
|
||||
atr20_status: "OK"
|
||||
total_heat_pct: 5.5
|
||||
expected_edge: 2.1
|
||||
expected_output:
|
||||
investment_action: "BUY"
|
||||
grade: "A"
|
||||
required_order_fields: ["account", "ticker", "limit_price", "quantity", "stop_price", "stop_quantity", "take_profit_price", "take_profit_quantity"]
|
||||
rule_ids_used:
|
||||
- "risk_policy.master_prohibitions"
|
||||
- "data_contract.data_completeness_gate"
|
||||
- "position_sizing.volatility_targeting"
|
||||
- "output_schema.buy_proposal_template.validation"
|
||||
@@ -0,0 +1,4 @@
|
||||
{"case_id":"EX_BUY_001","type":"BUY","expected_action":"BUY","required_rules":["HF001_DATA_MATRIX_REQUIRED","HF002_ATR20_REQUIRED_FOR_QUANTITY","HF006_BUY_ORDER_SET_REQUIRED"]}
|
||||
{"case_id":"EX_SELL_001","type":"SELL","expected_action":"SELL","required_rules":["HF003_HOLDINGS_REQUIRED_FOR_SELL_QTY","spec/exit/stop_loss.yaml:stop_loss.relative_weakness_exit"]}
|
||||
{"case_id":"EX_REJECT_001","type":"REJECT","expected_action":"AVOID","required_rules":["HF005_TOTAL_HEAT_HARD_BLOCK"]}
|
||||
{"case_id":"EX_INSUFFICIENT_DATA_001","type":"INSUFFICIENT_DATA","expected_action":"INSUFFICIENT_DATA","required_rules":["HF001_DATA_MATRIX_REQUIRED","HF002_ATR20_REQUIRED_FOR_QUANTITY"]}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"schema_version": "2026-05-15-F6-compat-output",
|
||||
"analysis_date": "2026-05-15",
|
||||
"analysis_scope": {
|
||||
"scope_type": "SINGLE_TICKER",
|
||||
"tickers": ["005930"],
|
||||
"accounts": ["일반계좌"]
|
||||
},
|
||||
"data_basis": {
|
||||
"as_of": "2026-05-15 16:30 KST",
|
||||
"timezone": "Asia/Seoul",
|
||||
"sources": [
|
||||
{
|
||||
"name": "sample",
|
||||
"type": "CALCULATED",
|
||||
"status": "OK",
|
||||
"note": "schema validation sample"
|
||||
}
|
||||
]
|
||||
},
|
||||
"capture_read_ledger": [
|
||||
{
|
||||
"source": "sample",
|
||||
"account": "일반계좌",
|
||||
"screen_type": "unknown",
|
||||
"read_values": {},
|
||||
"confidence": 0,
|
||||
"applied_to_orders": false,
|
||||
"read_status": "NOT_PROVIDED",
|
||||
"next_source_to_check": "HTS 보유종목/현금 화면 캡처"
|
||||
}
|
||||
],
|
||||
"portfolio_decision": {
|
||||
"final_action": "WATCH",
|
||||
"grade": "C",
|
||||
"confidence_score": 50,
|
||||
"rationale": "검증용 샘플. 실제 투자 판단 아님."
|
||||
},
|
||||
"scores": {
|
||||
"quality_score": null,
|
||||
"valuation_score": null,
|
||||
"momentum_score": null,
|
||||
"risk_score": 50,
|
||||
"strategy_score": null,
|
||||
"portfolio_fit_score": null,
|
||||
"total_score": null,
|
||||
"score_formula": "not_calculated",
|
||||
"score_notes": ["검증 샘플이므로 점수 산출 생략"]
|
||||
},
|
||||
"position_sizing": {
|
||||
"status": "NOT_APPLICABLE",
|
||||
"recommended_position_size_pct": null,
|
||||
"max_allowed_position_size_pct": null,
|
||||
"risk_budget": null,
|
||||
"atr20": null,
|
||||
"calculated_quantity": null,
|
||||
"final_quantity": null,
|
||||
"reason": "WATCH 샘플로 수량 산출 대상 아님"
|
||||
},
|
||||
"risk_gate": {
|
||||
"status": "CAUTION",
|
||||
"cash_floor_status": "UNKNOWN",
|
||||
"total_heat_pct": null,
|
||||
"hard_stop_triggered": false,
|
||||
"triggered_rules": []
|
||||
},
|
||||
"data_completeness_matrix": [
|
||||
{
|
||||
"ticker": "005930",
|
||||
"name": "삼성전자",
|
||||
"price_status": "OK",
|
||||
"flow5d_status": "PARTIAL",
|
||||
"flow20d_status": "DATA_MISSING",
|
||||
"atr20_status": "DATA_MISSING",
|
||||
"dart_status": "NOT_APPLICABLE",
|
||||
"missing_fields": ["ATR20", "Flow20D"],
|
||||
"next_source_to_check": "KRX/Naver/Yahoo 21거래일 OHLC 및 수급",
|
||||
"allowed_action": "WATCH_ONLY"
|
||||
}
|
||||
],
|
||||
"decision_trace": [
|
||||
{
|
||||
"state": "DATA_COMPLETENESS_CHECK",
|
||||
"check_id": "DCC_SAMPLE_001",
|
||||
"rule_ref": "spec/09_decision_flow.yaml:decision_flow.states.DATA_COMPLETENESS_CHECK",
|
||||
"inputs_used": ["ticker", "ATR20", "Flow20D"],
|
||||
"result": "INSUFFICIENT_DATA",
|
||||
"selected_action": "WATCH",
|
||||
"blocked_actions": ["BUY"],
|
||||
"missing_inputs": ["ATR20", "Flow20D"],
|
||||
"tie_breaker_applied": "5: 보수적 행동"
|
||||
}
|
||||
],
|
||||
"orders": [],
|
||||
"prohibited_calculations": [
|
||||
{
|
||||
"item": "buy_quantity",
|
||||
"reason": "ATR20 DATA_MISSING",
|
||||
"next_source_to_check": "21거래일 OHLC"
|
||||
}
|
||||
],
|
||||
"triggered_rules": [
|
||||
{
|
||||
"file": "spec/00_execution_contract.yaml",
|
||||
"path": "master_prohibitions.P2_no_atr_extrapolation",
|
||||
"result": "BLOCK",
|
||||
"explanation": "ATR20 미확인으로 정수 매수수량 산출 금지"
|
||||
}
|
||||
],
|
||||
"missing_data": [
|
||||
{
|
||||
"field": "ATR20",
|
||||
"status": "DATA_MISSING",
|
||||
"next_source_to_check": "Yahoo/Naver OHLC"
|
||||
}
|
||||
],
|
||||
"invalidation_conditions": [
|
||||
{
|
||||
"condition": "ATR20 remains DATA_MISSING",
|
||||
"action": "NO_BUY_QUANTITY",
|
||||
"rule_ref": "spec/00_execution_contract.yaml:master_prohibitions.P2_no_atr_extrapolation"
|
||||
}
|
||||
],
|
||||
"evidence": [
|
||||
{
|
||||
"field": "ticker",
|
||||
"value": "005930",
|
||||
"source": "sample",
|
||||
"as_of": "2026-05-15",
|
||||
"data_tag": "계산값"
|
||||
}
|
||||
],
|
||||
"rule_ids_used": ["P2_no_atr_extrapolation"],
|
||||
"rules_used": [
|
||||
{
|
||||
"file": "spec/00_execution_contract.yaml",
|
||||
"path": "master_prohibitions.P2_no_atr_extrapolation",
|
||||
"result": "USED",
|
||||
"explanation": "정수 매수수량 산출 금지 규칙 확인"
|
||||
}
|
||||
],
|
||||
"summary": "검증용 WATCH 샘플. 실제 투자 판단이 아니다."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
case_id: EX_HOLD_001
|
||||
type: HOLD
|
||||
input_summary:
|
||||
current_holding_confirmed: true
|
||||
grade: "B"
|
||||
current_weight_within_band: true
|
||||
expected_edge: 1.8
|
||||
expected_output:
|
||||
investment_action: "HOLD"
|
||||
report_section: "current_holdings_analysis_report_template"
|
||||
reason: "보유수량·평단·현재가 확인 후 유지 조건 충족."
|
||||
@@ -0,0 +1,15 @@
|
||||
case_id: EX_INSUFFICIENT_DATA_001
|
||||
type: INSUFFICIENT_DATA
|
||||
input_summary:
|
||||
holdings_screen: "NOT_PROVIDED"
|
||||
cash_available: "DATA_MISSING"
|
||||
atr20_status: "DATA_MISSING"
|
||||
expected_output:
|
||||
investment_action: "INSUFFICIENT_DATA"
|
||||
prohibited_outputs:
|
||||
- "매도수량 숫자"
|
||||
- "매수수량 숫자"
|
||||
- "A등급"
|
||||
next_source_to_check:
|
||||
- "계좌 잔고 원장"
|
||||
- "21거래일 OHLC for ATR20"
|
||||
@@ -0,0 +1,14 @@
|
||||
case_id: EX_REJECT_001
|
||||
type: REJECT
|
||||
input_summary:
|
||||
strategy_score: 91
|
||||
total_heat_pct: 10.4
|
||||
cash_floor_status: "BLOCK"
|
||||
atr20_status: "OK"
|
||||
expected_output:
|
||||
investment_action: "AVOID"
|
||||
grade: "D"
|
||||
reason: "Risk hard block overrides high strategy score."
|
||||
triggered_rules:
|
||||
- "HF005_TOTAL_HEAT_HARD_BLOCK"
|
||||
- "spec/risk/aggregate_risk.yaml:risk_control.aggregate_risk_cap.threshold.hard_block"
|
||||
@@ -0,0 +1,21 @@
|
||||
case_id: EX_SELL_001
|
||||
type: SELL
|
||||
input_summary:
|
||||
confirmed_holding_quantity: 120
|
||||
price_status: "OK"
|
||||
flow20d_status: "OK"
|
||||
trend_status: "20일선 이탈"
|
||||
risk_trigger: "relative_weakness_exit"
|
||||
expected_output:
|
||||
investment_action: "SELL"
|
||||
required_fields:
|
||||
- "account"
|
||||
- "ticker"
|
||||
- "limit_price_krw"
|
||||
- "quantity"
|
||||
- "rationale"
|
||||
prohibited_if_missing:
|
||||
- "confirmed_holding_quantity"
|
||||
rule_ids_used:
|
||||
- "HF003_HOLDINGS_REQUIRED_FOR_SELL_QTY"
|
||||
- "spec/exit/stop_loss.yaml:stop_loss.relative_weakness_exit"
|
||||
@@ -0,0 +1,378 @@
|
||||
/**
|
||||
* gas_apex_alpha_watch.gs
|
||||
* ────────────────────────────────────────────────────────────────────────────
|
||||
* APEX 행위기반 커버리지 하네스 — 핵심 계산 엔진 (Impl)
|
||||
* [2026-05-30] BCH-V1 대응을 위해 분리된 순수 함수들
|
||||
*/
|
||||
|
||||
/**
|
||||
* PA2: ANTI_LATE_ENTRY_GATE_V2
|
||||
* [Python py_anti_late_entry_gate_v2 미러와 100% 동일 로직]
|
||||
*
|
||||
* @param {Array} holdings asResult.holdings
|
||||
* @param {Object} dfMap 종목별 데이터 피드
|
||||
* @return {Array} anti_late_entry_json
|
||||
*/
|
||||
function calcAntiLateEntryGateV2Impl_(holdings, dfMap) {
|
||||
var results = [];
|
||||
for (var i = 0; i < holdings.length; i++) {
|
||||
var h = holdings[i];
|
||||
var ticker = h.ticker || '';
|
||||
var df = dfMap[ticker] || {};
|
||||
|
||||
var close = Number(h.close || df.close || 0);
|
||||
var prevClose = Number(df.prevClose || 0);
|
||||
var ma20 = Number(df.ma20 || 0);
|
||||
var rsi14 = Number(df.rsi14 != null ? df.rsi14 : 50);
|
||||
var flowCredit = Number(df.flowCredit != null ? df.flowCredit : 0);
|
||||
var volume = Number(df.volume || 0);
|
||||
var avgVol5d = Number(df.avgVolume5d || 0);
|
||||
var frg5d = Number(df.frg5d || 0);
|
||||
var inst5d = Number(df.inst5d || 0);
|
||||
var ret5d = Number(df.ret5d || 0);
|
||||
var acGate = String(df.acGate || '');
|
||||
|
||||
var v1d = prevClose > 0 ? (close - prevClose) / prevClose * 100 : 0.0;
|
||||
var v5d = ret5d;
|
||||
|
||||
var distWs = 0.0;
|
||||
if (frg5d < 0) distWs += 2.0;
|
||||
if (inst5d < 0) distWs += 2.0;
|
||||
if (avgVol5d > 0 && volume > avgVol5d * 1.3) distWs += 1.5;
|
||||
if (prevClose > 0 && close < prevClose) distWs += 1.5;
|
||||
if (rsi14 > 70) distWs += 1.0;
|
||||
if (acGate === 'BLOCK') distWs += 1.0;
|
||||
|
||||
var gate1 = 'PASS';
|
||||
if (v1d >= 3.0) gate1 = 'BLOCK_CHASE';
|
||||
else if (v1d >= 1.5) gate1 = 'PULLBACK_WAIT';
|
||||
|
||||
var gate2 = 'PASS';
|
||||
if (v5d >= 8.0) gate2 = 'BLOCK_CHASE_5D';
|
||||
else if (v5d >= 5.0) gate2 = 'PULLBACK_WAIT_5D';
|
||||
|
||||
var gate3 = 'PASS';
|
||||
if (distWs >= 3.0) gate3 = 'BLOCK_DISTRIBUTION';
|
||||
else if (distWs >= 2.0) gate3 = 'PULLBACK_WAIT_DIST';
|
||||
|
||||
var hasBlock = (gate1 === 'BLOCK_CHASE' || gate2 === 'BLOCK_CHASE_5D' || gate3 === 'BLOCK_DISTRIBUTION');
|
||||
var hasPullback = (gate1 === 'PULLBACK_WAIT' || gate2 === 'PULLBACK_WAIT_5D' || gate3 === 'PULLBACK_WAIT_DIST');
|
||||
|
||||
var finalGate = 'PASS';
|
||||
if (hasBlock) finalGate = 'BLOCK';
|
||||
else if (hasPullback) finalGate = 'PULLBACK_WAIT';
|
||||
|
||||
var grade = 'B';
|
||||
if (finalGate === 'BLOCK') {
|
||||
grade = 'F';
|
||||
} else if (v1d < 0.5 && ma20 > 0 && close >= ma20 && close <= ma20 * 1.02 && flowCredit >= 0.55) {
|
||||
grade = 'A';
|
||||
} else if (v1d < 1.5 && ma20 > 0 && Math.abs(close - ma20) / ma20 <= 0.05) {
|
||||
grade = 'B';
|
||||
} else if (finalGate === 'PULLBACK_WAIT') {
|
||||
grade = 'C';
|
||||
} else if (v5d > 5.0) {
|
||||
grade = 'D';
|
||||
}
|
||||
|
||||
results.push({
|
||||
ticker: ticker,
|
||||
gate1_status: gate1,
|
||||
gate2_status: gate2,
|
||||
gate3_status: gate3,
|
||||
final_gate_status: finalGate,
|
||||
anti_late_entry_status: finalGate,
|
||||
entry_grade: grade,
|
||||
velocity_1d: Math.round(v1d * 100) / 100,
|
||||
velocity_5d: Math.round(v5d * 100) / 100,
|
||||
dist_weighted_sum: Math.round(distWs * 10) / 10
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* PA5: CONSISTENCY_VALIDATOR_V2
|
||||
* [P0 GAP 해소 - 데이터 정합성 검증]
|
||||
*/
|
||||
function calcConsistencyValidatorV2Impl_(hApex, asResult, cashFloorInfo, capturedAtIso, now) {
|
||||
var checks = [];
|
||||
var passed = [];
|
||||
var failed = [];
|
||||
var gapList = [];
|
||||
|
||||
// CV_01: sell_priority 방향 일관성
|
||||
var sellCandidates = hApex.sell_candidates_json || [];
|
||||
var tierOk = true;
|
||||
for (var i = 1; i < sellCandidates.length; i++) {
|
||||
if (sellCandidates[i].tier < sellCandidates[i-1].tier) {
|
||||
tierOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tierOk) passed.push('CV_01'); else failed.push({check_id: 'CV_01', reason: 'tier_reversal'});
|
||||
|
||||
// CV_02: 가격 순서 검증
|
||||
var prices = hApex.prices_json || [];
|
||||
var priceOk = true;
|
||||
for (var i = 0; i < prices.length; i++) {
|
||||
var p = prices[i];
|
||||
if (p.stop_price && p.current_price && p.stop_price >= p.current_price) priceOk = false;
|
||||
}
|
||||
if (priceOk) passed.push('CV_02'); else failed.push({check_id: 'CV_02', reason: 'price_hierarchy_violation'});
|
||||
|
||||
// CV_06: 수량 정수 검증
|
||||
var qtyOk = true;
|
||||
var bqi = hApex.buy_qty_inputs_json || [];
|
||||
for (var i = 0; i < bqi.length; i++) {
|
||||
if (bqi[i].final_qty && bqi[i].final_qty % 1 !== 0) qtyOk = false;
|
||||
}
|
||||
if (qtyOk) passed.push('CV_06'); else failed.push({check_id: 'CV_06', reason: 'float_quantity'});
|
||||
|
||||
// CV_08: 현금 계산 경로
|
||||
if (hApex.cash_ledger_basis === 'D2_ONLY') passed.push('CV_08');
|
||||
else failed.push({check_id: 'CV_08', reason: 'invalid_cash_basis'});
|
||||
|
||||
// Score 계산
|
||||
var score = Math.floor((passed.length / 12) * 100);
|
||||
var status = score >= 90 ? (score === 100 ? 'PASS' : 'WARNING') : 'BLOCK';
|
||||
|
||||
return {
|
||||
formula_id: 'CONSISTENCY_VALIDATOR_V2',
|
||||
consistency_score: score,
|
||||
cv_verdict: status === 'BLOCK' ? 'ABORT' : 'PASS',
|
||||
block_status: status,
|
||||
passed: passed,
|
||||
failed: failed,
|
||||
gap_list: gapList,
|
||||
consistency_report_json: { score: score, passed: passed, failed: failed }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* PA4: MACRO_EVENT_SYNCHRONIZER_V1
|
||||
*/
|
||||
function calcMacroEventSynchronizerV1Impl_(macroJson, eventRows) {
|
||||
var usdKrw = Number(macroJson.usd_krw || 0);
|
||||
var foreignSellDays = Number(macroJson.foreign_sell_consecutive_days || 0);
|
||||
|
||||
var score = 0;
|
||||
if (usdKrw > 1500) score += 20;
|
||||
else if (usdKrw > 1480) score += 15;
|
||||
|
||||
if (foreignSellDays >= 10) score += 20;
|
||||
else if (foreignSellDays >= 5) score += 15;
|
||||
|
||||
var regime = 'MACRO_NEUTRAL';
|
||||
var heatAdj = 0;
|
||||
if (score >= 60) { regime = 'MACRO_CRITICAL'; heatAdj = -3; }
|
||||
else if (score >= 40) { regime = 'MACRO_ELEVATED'; heatAdj = -1; }
|
||||
else if (score < 20) { regime = 'MACRO_FAVORABLE'; heatAdj = 1; }
|
||||
|
||||
return {
|
||||
formula_id: 'MACRO_EVENT_SYNCHRONIZER_V1',
|
||||
macro_risk_score: score,
|
||||
macro_risk_regime: regime,
|
||||
effective_heat_gate_adjustment: heatAdj,
|
||||
mega_sell_alert: false,
|
||||
macro_event_json: { score: score, regime: regime, heat_gate_adj: heatAdj }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* PA1: PREDICTIVE_ALPHA_ENGINE_V1
|
||||
*/
|
||||
function calcPredictiveAlphaEngineV1Impl_(holdings, dfMap, macroJson, mesResult, weightOverrides) {
|
||||
var results = [];
|
||||
for (var i = 0; i < holdings.length; i++) {
|
||||
var h = holdings[i];
|
||||
var ticker = h.ticker;
|
||||
var df = dfMap[ticker] || {};
|
||||
|
||||
var thesis = 0;
|
||||
if (df.close > df.ma20 && df.close < df.ma20 * 1.03) thesis += 20;
|
||||
if (df.flowCredit >= 0.55) thesis += 20;
|
||||
|
||||
var antithesis = 0;
|
||||
var v1d = df.prevClose > 0 ? (df.close - df.prevClose) / df.prevClose * 100 : 0;
|
||||
if (v1d >= 3.0) antithesis += 25;
|
||||
|
||||
var confidence = thesis - antithesis;
|
||||
var verdict = 'HOLD_NEUTRAL';
|
||||
if (confidence >= 40) verdict = 'STRONG_BUY_SIGNAL';
|
||||
else if (confidence >= 20) verdict = 'MODERATE_BUY_SIGNAL';
|
||||
else if (confidence < -30) verdict = 'EXIT_SIGNAL';
|
||||
else if (confidence < -10) verdict = 'TRIM_SIGNAL';
|
||||
|
||||
results.push({
|
||||
ticker: ticker,
|
||||
direction_confidence: confidence,
|
||||
thesis_score: thesis,
|
||||
antithesis_score: antithesis,
|
||||
synthesis_verdict: verdict,
|
||||
predictive_alpha_json: { confidence: confidence, verdict: verdict }
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* MACRO_REGIME_ADAPTIVE_GATE_V2
|
||||
*/
|
||||
function calcMacroRegimeAdaptiveGateV2Impl_(macroJson, mesResult, hApex) {
|
||||
var totalScore = mesResult.macro_risk_score || 0;
|
||||
var regime = 'MODERATE_RISK';
|
||||
var heatThreshold = 10.0;
|
||||
var sizeScale = 1.0;
|
||||
|
||||
if (totalScore >= 75) { regime = 'EXTREME_RISK'; heatThreshold = 5.0; sizeScale = 0.25; }
|
||||
else if (totalScore >= 50) { regime = 'HIGH_RISK'; heatThreshold = 7.0; sizeScale = 0.50; }
|
||||
else if (totalScore < 25) { regime = 'LOW_RISK'; heatThreshold = 12.0; sizeScale = 1.10; }
|
||||
|
||||
return {
|
||||
formula_id: 'MACRO_REGIME_ADAPTIVE_GATE_V2',
|
||||
total_mrag_score: totalScore,
|
||||
regime_label: regime,
|
||||
effective_heat_gate_threshold: heatThreshold,
|
||||
effective_position_size_scale: sizeScale,
|
||||
mrag_v2_json: { score: totalScore, regime: regime }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* applyAlegGate4And5Impl_
|
||||
*/
|
||||
function applyAlegGate4And5Impl_(alegRows, paeRows, hApex) {
|
||||
var results = [];
|
||||
var paeMap = {};
|
||||
for (var i = 0; i < paeRows.length; i++) paeMap[paeRows[i].ticker] = paeRows[i];
|
||||
|
||||
for (var i = 0; i < alegRows.length; i++) {
|
||||
var row = alegRows[i];
|
||||
var pae = paeMap[row.ticker] || {};
|
||||
|
||||
if (pae.synthesis_verdict === 'EXIT_SIGNAL' || pae.synthesis_verdict === 'TRIM_SIGNAL') {
|
||||
row.gate4_status = 'BLOCK_PAE';
|
||||
row.final_gate_status = 'BLOCK';
|
||||
row.anti_late_entry_status = 'BLOCK';
|
||||
} else {
|
||||
row.gate4_status = 'PASS';
|
||||
}
|
||||
results.push(row);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suite Aggregators
|
||||
*/
|
||||
function applyApexMacroAlphaSuiteImpl_(holdings, dfMap, hApex) {
|
||||
// Placeholder for macro alpha suite
|
||||
return hApex;
|
||||
}
|
||||
|
||||
function applyApexMacroEventSuiteImpl_(hApex) {
|
||||
// Placeholder for macro event suite
|
||||
return hApex;
|
||||
}
|
||||
|
||||
function applyApexPredictiveAlphaSuiteImpl_(holdings, dfMap, hApex) {
|
||||
var macroJson = hApex.macro_event_json || {};
|
||||
var mesResult = hApex.macro_event_json || {};
|
||||
var paeRows = calcPredictiveAlphaEngineV1Impl_(holdings, dfMap, macroJson, mesResult, null);
|
||||
hApex.predictive_alpha_json = paeRows;
|
||||
|
||||
// portfolio_alpha_confidence: mean direction_confidence across all holdings
|
||||
var sum = 0, n = 0;
|
||||
(paeRows || []).forEach(function(r) {
|
||||
if (typeof r.direction_confidence === 'number') { sum += r.direction_confidence; n++; }
|
||||
});
|
||||
hApex.portfolio_alpha_confidence = n > 0 ? Math.round(sum / n * 100) / 100 : 0;
|
||||
|
||||
return hApex;
|
||||
}
|
||||
|
||||
function applyApexWatchBreakoutSuiteImpl_(holdings, dfMap, hApex) {
|
||||
var slgRows = hApex.satellite_lifecycle_gate_json || [];
|
||||
var aleRows = hApex.anti_late_entry_json || [];
|
||||
hApex.watch_breakout_candidates_json = calcWatchBreakoutRealtimeGateV1_(holdings, dfMap, slgRows, aleRows);
|
||||
return hApex;
|
||||
}
|
||||
|
||||
// ---- TASK-006: ANTI_LATE_ENTRY_GATE_V2_CALIBRATED ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function calibrateAntiLateEntryV2_(proposalHistory, captureDate) {
|
||||
// RC 수정: velocity 버킷별 T+5 승률 계산 (실측 표본 >= 30 충족 후 활성화)
|
||||
var buckets = { LOW: {n:0,wins:0}, MID: {n:0,wins:0}, HIGH: {n:0,wins:0} };
|
||||
var totalBuys = 0, chaseBuys = 0;
|
||||
(proposalHistory || []).forEach(function(p) {
|
||||
if (p.origin === 'REPLAY' || p.action !== 'BUY') return;
|
||||
if (p.realized_return_pct_t5 === undefined) return; // 미채움 제외
|
||||
totalBuys++;
|
||||
var v = parseFloat(p.velocity_1d || 0);
|
||||
var win = parseFloat(p.realized_return_pct_t5 || 0) > 0;
|
||||
var bucket = v < 1.0 ? 'LOW' : v < 3.0 ? 'MID' : 'HIGH';
|
||||
buckets[bucket].n++;
|
||||
if (win) buckets[bucket].wins++;
|
||||
if (v >= 3.0) chaseBuys++;
|
||||
});
|
||||
var minSamples = 30;
|
||||
var validated = Object.keys(buckets).every(function(k) { return buckets[k].n >= minSamples; });
|
||||
return {
|
||||
formula_id: 'ANTI_LATE_ENTRY_GATE_V2_CALIBRATED',
|
||||
validated: validated,
|
||||
unvalidated_label: validated ? null : '[UNVALIDATED_LIVE: n<30 per bucket]',
|
||||
chase_entry_rate_pct: totalBuys > 0 ? (chaseBuys / totalBuys * 100).toFixed(1) : null,
|
||||
buckets: buckets,
|
||||
threshold_source: validated ? 'DYNAMIC' : 'EXPERT_PRIOR',
|
||||
velocity_1d_block_pct: 3.0
|
||||
};
|
||||
}
|
||||
|
||||
// ---- TASK-007: DISTRIBUTION_BLOCK_EFFECTIVENESS_V1 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function trackDistributionBlockEffectiveness_(proposalHistory) {
|
||||
var blocked = (proposalHistory || []).filter(function(p) {
|
||||
return p.blocked_reason === 'DISTRIBUTION_CONFIRMED' || p.blocked_reason === 'DISTRIBUTION_BLOCK';
|
||||
});
|
||||
var avoidedLoss = blocked.filter(function(p) {
|
||||
return p.t5_return_if_not_blocked !== undefined && parseFloat(p.t5_return_if_not_blocked) < 0;
|
||||
});
|
||||
var blockedN = blocked.length;
|
||||
var avoidedLossRate = blockedN > 0 ? (avoidedLoss.length / blockedN) : null;
|
||||
return {
|
||||
formula_id: 'DISTRIBUTION_BLOCK_EFFECTIVENESS_V1',
|
||||
blocked_sample_count: blockedN,
|
||||
avoided_loss_rate: avoidedLossRate,
|
||||
target_avoided_loss_rate: 0.60,
|
||||
effectiveness_label: blockedN < 30
|
||||
? '[UNVALIDATED_LOW_N: n=' + blockedN + ' < 30]'
|
||||
: (avoidedLossRate >= 0.60 ? 'EFFECTIVE' : 'REVIEW_THRESHOLD')
|
||||
};
|
||||
}
|
||||
|
||||
// ---- TASK-010: SMART_MONEY_LIQUIDITY_OUTCOME_LINK_V1 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function linkSmartMoneyOutcome_(proposalHistory) {
|
||||
var buckets = {};
|
||||
(proposalHistory || []).forEach(function(p) {
|
||||
if (p.origin === 'REPLAY' || !p.liquidity_label) return;
|
||||
var lbl = p.liquidity_label;
|
||||
if (!buckets[lbl]) buckets[lbl] = {returns:[], slippages:[]};
|
||||
if (p.realized_return_pct_t5 !== undefined) buckets[lbl].returns.push(parseFloat(p.realized_return_pct_t5));
|
||||
if (p.slippage_pct !== undefined) buckets[lbl].slippages.push(parseFloat(p.slippage_pct));
|
||||
});
|
||||
var table = Object.keys(buckets).map(function(lbl) {
|
||||
var d = buckets[lbl];
|
||||
var n = d.returns.length;
|
||||
var wins = d.returns.filter(function(r){return r>0;}).length;
|
||||
return {
|
||||
liquidity_label: lbl,
|
||||
sample_count: n,
|
||||
t5_avg_return_pct: n > 0 ? d.returns.reduce(function(a,b){return a+b;},0)/n : null,
|
||||
t5_win_rate: n > 0 ? wins/n : null,
|
||||
label: n < 30 ? '[UNVALIDATED: n=' + n + ' < 30]' : 'VALIDATED'
|
||||
};
|
||||
});
|
||||
return {formula_id: 'SMART_MONEY_LIQUIDITY_OUTCOME_LINK_V1', table: table};
|
||||
}
|
||||
@@ -0,0 +1,705 @@
|
||||
// Consolidated runtime core: macro flow + macro calc + consistency
|
||||
|
||||
|
||||
// ---- from gas_apex_macro_flow.gs ----
|
||||
|
||||
function applyApexMacroAlphaSuiteImpl_(holdings, dfMap, hApex) {
|
||||
Logger.log('[HARNESS_SUB] L3-B2a-i: applyApexMacroEventSuite_');
|
||||
hApex = applyApexMacroEventSuite_(hApex);
|
||||
Logger.log('[HARNESS_SUB] L3-B2a-ii: applyApexPredictiveAlphaSuite_');
|
||||
hApex = applyApexPredictiveAlphaSuite_(holdings, dfMap, hApex);
|
||||
|
||||
// [Phase 2] SMART_MONEY_DISTRIBUTION_GUARD_V1: T+5 예측 적중률 연동 매수 차단
|
||||
if (typeof hApex.prediction_accuracy_rate === 'number' && hApex.prediction_accuracy_rate < 50) {
|
||||
Logger.log('[HARNESS_SUB] Phase 2: prediction_accuracy_rate < 50% (' + hApex.prediction_accuracy_rate + '%). 신규 매수 전면 차단.');
|
||||
hApex.global_buy_allowed = false;
|
||||
(hApex.buy_permission_json || []).forEach(function(bp) {
|
||||
if (bp.buy_permission_state !== 'BLOCKED') {
|
||||
bp.buy_permission_state = 'BLOCKED';
|
||||
bp.block_reason = (bp.block_reason ? bp.block_reason + ' | ' : '') + 'PREDICTION_ACCURACY_LOW(<50%)';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hApex;
|
||||
}
|
||||
|
||||
function applyApexMacroEventSuiteImpl_(hApex) {
|
||||
var macroJson = getMacroJson();
|
||||
var eventRiskFullRows = (function() {
|
||||
try { return getEventRiskJson().events || []; } catch(e) { return []; }
|
||||
})();
|
||||
var mesResult = calcMacroEventSynchronizerV1_(macroJson, eventRiskFullRows);
|
||||
hApex.macro_event_json = mesResult;
|
||||
hApex.macro_risk_score = mesResult.macro_risk_score;
|
||||
hApex.macro_risk_regime = mesResult.macro_risk_regime;
|
||||
hApex.mega_sell_alert = mesResult.mega_sell_alert;
|
||||
|
||||
var mragResult = calcMacroRegimeAdaptiveGate_(macroJson, mesResult, hApex);
|
||||
hApex.mrag_v2_json = mragResult;
|
||||
if (mesResult.heat_gate_adj && mesResult.heat_gate_adj !== 0) {
|
||||
var me1Threshold = (hApex.heat_gate_threshold_pct || 12) + mesResult.heat_gate_adj;
|
||||
hApex.effective_heat_gate_threshold = Math.min(me1Threshold, mragResult.effective_heat_gate_threshold);
|
||||
} else {
|
||||
hApex.effective_heat_gate_threshold = mragResult.effective_heat_gate_threshold;
|
||||
}
|
||||
hApex.effective_position_size_scale = mragResult.effective_position_size_scale;
|
||||
if (mragResult.stale_events_count > 0) {
|
||||
hApex.stale_events_alert = mragResult.stale_events;
|
||||
}
|
||||
|
||||
var fomcDaysRem = mesResult.fomc_days_remaining;
|
||||
var usCpiDaysRem = mesResult.us_cpi_days_remaining;
|
||||
var ipoDaysRem = mesResult.large_ipo_days_remaining;
|
||||
|
||||
var fomcGateActive = typeof fomcDaysRem === 'number' && fomcDaysRem <= 7;
|
||||
var usCpiGateActive = typeof usCpiDaysRem === 'number' && usCpiDaysRem <= 2;
|
||||
var ipoGateActive = typeof ipoDaysRem === 'number' && ipoDaysRem <= 3;
|
||||
|
||||
hApex.fomc_position_size_gate = fomcGateActive ? 'ACTIVE' : 'INACTIVE';
|
||||
hApex.us_cpi_position_size_gate = usCpiGateActive ? 'ACTIVE' : 'INACTIVE';
|
||||
hApex.ipo_position_size_gate = ipoGateActive ? 'ACTIVE' : 'INACTIVE';
|
||||
|
||||
if (fomcGateActive) {
|
||||
(hApex.buy_permission_json || []).forEach(function(bp) {
|
||||
bp.fomc_size_limit = 0.5;
|
||||
bp.fomc_size_gate_reason = 'FOMC_' + fomcDaysRem + 'D_REMAINING';
|
||||
});
|
||||
}
|
||||
if (usCpiGateActive) {
|
||||
(hApex.buy_permission_json || []).forEach(function(bp) {
|
||||
bp.us_cpi_size_limit = 0.5;
|
||||
bp.us_cpi_size_gate_reason = 'US_CPI_' + usCpiDaysRem + 'D_REMAINING';
|
||||
});
|
||||
}
|
||||
if (ipoGateActive) {
|
||||
(hApex.buy_permission_json || []).forEach(function(bp) {
|
||||
bp.ipo_size_limit = 0.7;
|
||||
bp.ipo_size_gate_reason = 'LARGE_IPO_' + ipoDaysRem + 'D_REMAINING';
|
||||
});
|
||||
}
|
||||
return hApex;
|
||||
}
|
||||
|
||||
// ---- from gas_apex_macro_calc_core.gs ----
|
||||
|
||||
|
||||
|
||||
function calcMacroEventSynchronizerV1Impl_(macroJson, eventRows) {
|
||||
var indicators = macroJson.indicators || [];
|
||||
var byName = {};
|
||||
indicators.forEach(function(m) { byName[m.Name] = m; });
|
||||
|
||||
var usdKrw = typeof macroJson.usd_krw === 'number' ? macroJson.usd_krw : 0;
|
||||
var vix = typeof macroJson.vix === 'number' ? macroJson.vix : 0;
|
||||
var sp500Ret5d = typeof macroJson.sp500_ret5d === 'number' ? macroJson.sp500_ret5d : 0;
|
||||
|
||||
// 외국인 순매도 연속일 (macro 시트 누적)
|
||||
var fscRow = byName['Foreign_Sell_Consecutive_Days'] || byName['ForeignSellConsecutiveDays'] || {};
|
||||
var foreignSellDays = typeof fscRow.Close === 'number' ? Math.round(fscRow.Close) : 0;
|
||||
|
||||
// 외국인 당일 순매도 금액
|
||||
var fskRow = byName['Foreign_Sell_KRW_Today'] || byName['ForeignSellKRWToday'] || {};
|
||||
var foreignSellKrwToday = typeof fskRow.Close === 'number' ? fskRow.Close : 0;
|
||||
|
||||
// 국내 CPI
|
||||
var cpiRow = byName['Domestic_CPI'] || byName['CPI_Domestic'] || {};
|
||||
var domesticCpi = typeof cpiRow.Close === 'number' ? cpiRow.Close : 0;
|
||||
|
||||
// FOMC / US_CPI / IPO 잔여 일수 (event_risk 시트)
|
||||
var fomcDaysRemaining = null;
|
||||
var usCpiDaysRemaining = null;
|
||||
var largeIpoDaysRemaining = null;
|
||||
var eventRowsSafe = Array.isArray(eventRows) ? eventRows : [];
|
||||
|
||||
function _nearestDays(typeStr) {
|
||||
var list = eventRowsSafe.filter(function(e) {
|
||||
var t = (e.Type || e.type || '').toUpperCase();
|
||||
var d = typeof e.DaysLeft === 'number' ? e.DaysLeft : (typeof e.daysLeft === 'number' ? e.daysLeft : -1);
|
||||
return t === typeStr && d >= 0;
|
||||
});
|
||||
if (!list.length) return null;
|
||||
list.sort(function(a, b) {
|
||||
return (a.DaysLeft || a.daysLeft || 999) - (b.DaysLeft || b.daysLeft || 999);
|
||||
});
|
||||
return list[0].DaysLeft || list[0].daysLeft || null;
|
||||
}
|
||||
|
||||
fomcDaysRemaining = _nearestDays('FOMC');
|
||||
usCpiDaysRemaining = _nearestDays('US_CPI');
|
||||
largeIpoDaysRemaining = _nearestDays('IPO');
|
||||
|
||||
// ── macro_risk_score 산출 (max 100) ─────────────────────────────────────────
|
||||
var breakdown = [];
|
||||
var macroRiskScore = 0;
|
||||
|
||||
function addMacroScore(label, condition, score) {
|
||||
if (condition) macroRiskScore += score;
|
||||
breakdown.push({ factor: label, score: condition ? score : 0, triggered: !!condition });
|
||||
}
|
||||
|
||||
addMacroScore('usd_krw_critical', usdKrw > 1500, 20);
|
||||
addMacroScore('usd_krw_weak', usdKrw > 1480 && usdKrw <= 1500, 15);
|
||||
addMacroScore('foreign_mega', foreignSellDays >= 10, 20);
|
||||
addMacroScore('foreign_high', foreignSellDays >= 5 && foreignSellDays < 10, 15);
|
||||
addMacroScore('fomc_near', fomcDaysRemaining !== null && fomcDaysRemaining <= 5, 15);
|
||||
addMacroScore('us_cpi_near', usCpiDaysRemaining !== null && usCpiDaysRemaining <= 2, 10);
|
||||
addMacroScore('cpi_high', domesticCpi > 2.5, 10);
|
||||
addMacroScore('vix_elevated', vix > 20, 10);
|
||||
addMacroScore('us500_drop', sp500Ret5d < -3.0, 10);
|
||||
macroRiskScore = Math.min(100, macroRiskScore);
|
||||
|
||||
// ── macro_risk_regime 분류 ───────────────────────────────────────────────────
|
||||
var macroRiskRegime, heatGateAdj;
|
||||
if (macroRiskScore >= 60) { macroRiskRegime = 'MACRO_CRITICAL'; heatGateAdj = -3; }
|
||||
else if (macroRiskScore >= 40) { macroRiskRegime = 'MACRO_ELEVATED'; heatGateAdj = -1; }
|
||||
else if (macroRiskScore >= 20) { macroRiskRegime = 'MACRO_NEUTRAL'; heatGateAdj = 0; }
|
||||
else { macroRiskRegime = 'MACRO_FAVORABLE'; heatGateAdj = +1; }
|
||||
|
||||
// ── event_matrix ────────────────────────────────────────────────────────────
|
||||
var eventMatrix = [];
|
||||
if (fomcDaysRemaining !== null && fomcDaysRemaining <= 7) {
|
||||
eventMatrix.push({ event: 'FOMC_WEEK', buy_gate_downgrade: true, sell_block: false,
|
||||
days_remaining: fomcDaysRemaining });
|
||||
}
|
||||
// US CPI 발표 2일 이내 — 신규매수 자제 (예상치 상회 시 급락 위험)
|
||||
if (usCpiDaysRemaining !== null && usCpiDaysRemaining <= 2) {
|
||||
eventMatrix.push({ event: 'US_CPI_IMMINENT', buy_gate_downgrade: true, sell_block: false,
|
||||
days_remaining: usCpiDaysRemaining,
|
||||
note: '미국 CPI 발표 임박 — 예상치 대비 서프라이즈 위험. 신규매수 자제' });
|
||||
}
|
||||
// 대형 IPO 5일 이내 — 공모자금 쏠림으로 시장 유동성 흡수 주의
|
||||
if (largeIpoDaysRemaining !== null && largeIpoDaysRemaining <= 5) {
|
||||
eventMatrix.push({ event: 'LARGE_IPO_WINDOW', buy_gate_downgrade: true, sell_block: false,
|
||||
days_remaining: largeIpoDaysRemaining,
|
||||
note: '대형 IPO 상장 임박 — 공모자금 유동성 흡수. 소형주·위성 포지션 매수 자제' });
|
||||
}
|
||||
|
||||
// mega_sell_alert: 외국인 순매도 >= 1조원
|
||||
var megaSellAlert = foreignSellKrwToday >= 1000000000000;
|
||||
var buyGateBlockUntil = null;
|
||||
if (megaSellAlert) {
|
||||
var blockDate = new Date();
|
||||
var bizAdded = 0;
|
||||
while (bizAdded < 3) {
|
||||
blockDate.setDate(blockDate.getDate() + 1);
|
||||
var wd = blockDate.getDay();
|
||||
if (wd !== 0 && wd !== 6) bizAdded++;
|
||||
}
|
||||
buyGateBlockUntil = Utilities.formatDate(blockDate, 'Asia/Seoul', 'yyyy-MM-dd');
|
||||
eventMatrix.push({ event: 'MEGA_SELL_ALERT', foreign_sell_krw: foreignSellKrwToday,
|
||||
buy_gate_block_until: buyGateBlockUntil });
|
||||
}
|
||||
|
||||
return {
|
||||
macro_risk_score: macroRiskScore,
|
||||
macro_risk_regime: macroRiskRegime,
|
||||
macro_risk_breakdown: breakdown,
|
||||
foreign_sell_consecutive_days: foreignSellDays,
|
||||
foreign_sell_krw_today: foreignSellKrwToday,
|
||||
mega_sell_alert: megaSellAlert,
|
||||
buy_gate_block_until: buyGateBlockUntil,
|
||||
effective_heat_gate_adjustment: heatGateAdj,
|
||||
heat_gate_adj: heatGateAdj,
|
||||
fomc_days_remaining: fomcDaysRemaining,
|
||||
us_cpi_days_remaining: usCpiDaysRemaining,
|
||||
large_ipo_days_remaining: largeIpoDaysRemaining,
|
||||
event_matrix: eventMatrix,
|
||||
formula_id: 'MACRO_EVENT_SYNCHRONIZER_V1'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function calcMacroRegimeAdaptiveGateV2Impl_(macroJson, mesResult, hApex) {
|
||||
var macro = macroJson || {};
|
||||
var mes = mesResult || {};
|
||||
|
||||
// ── LAYER_1: 미시 리스크 (Market Internals, 0~25) ──────────────────
|
||||
var l1 = 0;
|
||||
var vkospi = toNumber_(macro['vkospi'] || macro.vkospi) || 0;
|
||||
var mrsScoreL1 = toNumber_(macro['mrs_score'] || macro.mrs_score || (hApex && hApex.mrs_score)) || 0;
|
||||
var breadthAdv = toNumber_(macro['breadth_advance_decline'] || macro.breadth_advance_decline) || 0;
|
||||
if (breadthAdv > 0 && breadthAdv < 0.45) l1 += 10; // 하락 종목 비율 55% 초과
|
||||
if (vkospi > 30) l1 += 10; // VKOSPI 공포
|
||||
if (mrsScoreL1 <= 3) l1 += 5; // MRS 저점
|
||||
l1 = Math.min(l1, 25);
|
||||
|
||||
// ── LAYER_2: 거시 리스크 (Macro, 0~25) ────────────────────────────
|
||||
var l2 = 0;
|
||||
var macroRiskScore = toNumber_(mes.macro_risk_score) || 0;
|
||||
l2 = Math.min(25, Math.round(macroRiskScore / 100 * 25));
|
||||
|
||||
// ── LAYER_3: 글로벌 리스크 (Global, 0~25) ─────────────────────────
|
||||
var l3 = 0;
|
||||
var usRetWeek = toNumber_(macro['us500_1w_change'] || macro.us500_1w_change) || 0;
|
||||
var vix = toNumber_(macro['vix'] || macro.vix) || 0;
|
||||
var globalOvrd = String(macro['global_risk_override'] || '').toUpperCase();
|
||||
if (usRetWeek < -3) l3 += 10; // S&P500 주간 -3% 이하
|
||||
if (vix >= 30) l3 += 10; // VIX 공포
|
||||
else if (vix >= 25) l3 += 7; // VIX 경계
|
||||
if (globalOvrd === 'MANUAL_HIGH') l3 = 25; // 수동 override
|
||||
l3 = Math.min(l3, 25);
|
||||
|
||||
// ── LAYER_4: 이벤트 리스크 (Event, 0~25) ──────────────────────────
|
||||
var l4 = 0;
|
||||
var fomcDays = typeof mes.fomc_days_remaining === 'number' ? mes.fomc_days_remaining : 99;
|
||||
var usCpiDays = typeof mes.us_cpi_days_remaining === 'number' ? mes.us_cpi_days_remaining : 99;
|
||||
var largeIpoDays = typeof mes.large_ipo_days_remaining === 'number' ? mes.large_ipo_days_remaining : 99;
|
||||
var megaSell = mes.mega_sell_alert === true;
|
||||
if (fomcDays <= 5) l4 += 15;
|
||||
else if (fomcDays <= 7) l4 += 8;
|
||||
if (megaSell) l4 += 10;
|
||||
// US CPI: 발표 2일 이내 +8, 3일 이내 +4 (금리 경로 재평가 리스크)
|
||||
if (usCpiDays <= 2) l4 += 8;
|
||||
else if (usCpiDays <= 3) l4 += 4;
|
||||
// 대형 IPO: 상장 3일 이내 +5 (공모자금 유동성 흡수)
|
||||
if (largeIpoDays <= 3) l4 += 5;
|
||||
l4 = Math.min(l4, 25);
|
||||
|
||||
var totalScore = l1 + l2 + l3 + l4;
|
||||
|
||||
// ── HEAT_GATE 임계값 / POSITION_SIZE_SCALE 조정 ────────────────────
|
||||
var effectiveHeatThreshold, effectivePositionScale, regimeLabel;
|
||||
if (totalScore >= 80) {
|
||||
effectiveHeatThreshold = 5; effectivePositionScale = 0.25; regimeLabel = 'EVENT_SHOCK';
|
||||
} else if (totalScore >= 60) {
|
||||
effectiveHeatThreshold = 7; effectivePositionScale = 0.50; regimeLabel = 'RISK_OFF';
|
||||
} else if (totalScore >= 40) {
|
||||
effectiveHeatThreshold = 10; effectivePositionScale = 1.00; regimeLabel = 'NEUTRAL';
|
||||
} else {
|
||||
effectiveHeatThreshold = 12; effectivePositionScale = 1.10; regimeLabel = 'RISK_ON';
|
||||
}
|
||||
|
||||
// ── 이벤트 날짜 검증 (STALE_EVENT 탐지) ────────────────────────────
|
||||
var eventDateResults = [];
|
||||
var staleEvents = [];
|
||||
var analysisDate = new Date();
|
||||
(mes.events_used || []).forEach(function(ev) {
|
||||
if (!ev || !ev.event_date) return;
|
||||
var evDate = new Date(ev.event_date);
|
||||
var valid = evDate >= analysisDate;
|
||||
var r = { event_type: ev.event_type || 'UNKNOWN', event_date: ev.event_date, valid: valid,
|
||||
status: valid ? 'VALID' : 'STALE_EVENT' };
|
||||
if (!valid) staleEvents.push(r);
|
||||
eventDateResults.push(r);
|
||||
});
|
||||
|
||||
return {
|
||||
micro_risk_score: l1,
|
||||
macro_risk_score_normalized: l2,
|
||||
global_risk_score: l3,
|
||||
event_risk_score: l4,
|
||||
total_mrag_score: totalScore,
|
||||
effective_heat_gate_threshold: effectiveHeatThreshold,
|
||||
effective_position_size_scale: effectivePositionScale,
|
||||
regime_label: regimeLabel,
|
||||
event_date_validation_results: eventDateResults,
|
||||
stale_events: staleEvents,
|
||||
stale_events_count: staleEvents.length,
|
||||
formula_id: 'MACRO_REGIME_ADAPTIVE_GATE_V2'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// ---- from gas_apex_consistency_core.gs ----
|
||||
|
||||
|
||||
function calcConsistencyValidatorV2Impl_(hApex, asResult, cashFloorInfo, capturedAtIso, now) {
|
||||
var passed = [], failed = [], gapList = [];
|
||||
|
||||
function chk(id, name, testFn) {
|
||||
try {
|
||||
var r = testFn();
|
||||
if (r.ok) {
|
||||
passed.push(id);
|
||||
} else {
|
||||
failed.push({ check_id: id, name: name, reason: r.reason || 'failed' });
|
||||
if (r.gaps) r.gaps.forEach(function(g) { gapList.push(g); });
|
||||
}
|
||||
} catch(e) {
|
||||
failed.push({ check_id: id, name: name, reason: 'exception:' + e.message });
|
||||
}
|
||||
}
|
||||
|
||||
// CV_01: sell_candidates tier 비감소
|
||||
chk('CV_01', 'sell_priority 방향 일관성', function() {
|
||||
var cands = hApex.sell_candidates_json || [];
|
||||
for (var i = 1; i < cands.length; i++) {
|
||||
var ta = cands[i-1].tier, tb = cands[i].tier;
|
||||
if (typeof ta === 'number' && typeof tb === 'number' && tb < ta) {
|
||||
return { ok: false, reason: 'tier_reversal idx=' + i + '(' + tb + '<' + ta + ')' };
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_02: stop < close < tp1 (< tp2)
|
||||
chk('CV_02', '가격 순서 검증', function() {
|
||||
var prices = hApex.prices_json || [];
|
||||
for (var i = 0; i < prices.length; i++) {
|
||||
var p = prices[i];
|
||||
var stop = p.stop_price || 0, curr = p.current_price || p.close || 0, tp1 = p.tp1_price || 0;
|
||||
if (stop > 0 && curr > 0 && stop >= curr) {
|
||||
return { ok: false, reason: p.ticker + ':stop(' + stop + ')>=close(' + curr + ')' };
|
||||
}
|
||||
if (curr > 0 && tp1 > 0 && curr >= tp1) {
|
||||
return { ok: false, reason: p.ticker + ':close(' + curr + ')>=tp1(' + tp1 + ')' };
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_03: heat vs weight 비례성 (구조 확인용)
|
||||
chk('CV_03', 'heat vs 보유 비중 일치', function() {
|
||||
var holdings = asResult.holdings || [];
|
||||
// heat_pct는 손실위험 기준, weight_pct는 평가비중 — 직접 비교 불가
|
||||
// 보유 종목 존재 확인 (구조 레벨 검증)
|
||||
if (holdings.length > 0 && !hApex.execution_quality_json) {
|
||||
return { ok: false, reason: 'execution_quality_json 없음 (보유종목 있음)' };
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_04: enum 유효성 (synthesis_verdict, rs_verdict)
|
||||
chk('CV_04', 'enum 값 유효성', function() {
|
||||
var VALID_SYNTH = ['STRONG_BUY_SIGNAL','MODERATE_BUY_SIGNAL','HOLD_NEUTRAL','TRIM_SIGNAL','EXIT_SIGNAL'];
|
||||
var VALID_RS = ['LEADER','NEUTRAL','LAGGARD','BROKEN','UNKNOWN','N/A',''];
|
||||
var paeList = hApex.predictive_alpha_json || [];
|
||||
for (var i = 0; i < paeList.length; i++) {
|
||||
var v = paeList[i].synthesis_verdict;
|
||||
if (v && VALID_SYNTH.indexOf(v) < 0) {
|
||||
return { ok: false, reason: paeList[i].ticker + ':invalid synthesis_verdict=' + v };
|
||||
}
|
||||
}
|
||||
var saqgList = hApex.saqg_json || [];
|
||||
for (var j = 0; j < saqgList.length; j++) {
|
||||
var rv = saqgList[j].rs_verdict;
|
||||
if (rv && VALID_RS.indexOf(rv) < 0) {
|
||||
return { ok: false, reason: saqgList[j].ticker + ':invalid rs_verdict=' + rv };
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_05: 상호 충돌 게이트 탐지 [PROPOSAL47_B5 확장: MACRO_CRITICAL 추가]
|
||||
chk('CV_05', '상호 충돌 게이트 탐지', function() {
|
||||
var sfg = hApex.satellite_failure_gate_json || {};
|
||||
var sfgTriggered = sfg.sfg_v1 === 'TRIGGERED';
|
||||
var megaSell = hApex.mega_sell_alert === true;
|
||||
var macroCritical = hApex.macro_risk_regime === 'MACRO_CRITICAL';
|
||||
var buyPerms = hApex.buy_permission_json || [];
|
||||
for (var i = 0; i < buyPerms.length; i++) {
|
||||
var bp = buyPerms[i];
|
||||
var eligible = bp.buy_permission_state === 'ELIGIBLE' || bp.buy_permission_state === 'STAGED_BUY';
|
||||
if (eligible && sfgTriggered) {
|
||||
return { ok: false, reason: bp.ticker + ':buy=ELIGIBLE but sfg=TRIGGERED' };
|
||||
}
|
||||
if (eligible && megaSell && hApex.buy_gate_block_until) {
|
||||
return { ok: false, reason: bp.ticker + ':buy=ELIGIBLE but mega_sell_alert=true' };
|
||||
}
|
||||
if (eligible && macroCritical) {
|
||||
return { ok: false, reason: bp.ticker + ':buy=ELIGIBLE but macro_risk_regime=MACRO_CRITICAL' };
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_06: 수량 정수 검증
|
||||
chk('CV_06', '수량 정수 검증', function() {
|
||||
var sqList = hApex.smart_sell_quantities_json || [];
|
||||
for (var i = 0; i < sqList.length; i++) {
|
||||
var sq = sqList[i];
|
||||
if (typeof sq.sell_qty === 'number' && sq.sell_qty !== Math.floor(sq.sell_qty)) {
|
||||
return { ok: false, reason: sq.ticker + ':sell_qty 소수점=' + sq.sell_qty };
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_07: 데이터 신선도
|
||||
chk('CV_07', '날짜 신선도', function() {
|
||||
if (!capturedAtIso) return { ok: true };
|
||||
var capMs = new Date(capturedAtIso).getTime();
|
||||
if (isNaN(capMs)) return { ok: true };
|
||||
var nowMs = (now && now.getTime) ? now.getTime() : Date.now();
|
||||
var diffDays = (nowMs - capMs) / 86400000;
|
||||
if (diffDays > 3) return { ok: false, reason: 'STALE_BLOCK:' + Math.round(diffDays) + '일 경과' };
|
||||
if (diffDays > 1) return { ok: false, reason: 'STALE_WARN:' + Math.round(diffDays) + '일 경과' };
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_08: 현금 계산 경로 — GAS는 settlementCashD2Krw만 사용 (항상 통과)
|
||||
chk('CV_08', '현금 계산 경로', function() {
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_09: 라우팅 completeness — Sprint B 핵심 출력 존재 확인
|
||||
chk('CV_09', '라우팅 completeness', function() {
|
||||
var required = ['data_freshness_json','satellite_lifecycle_gate_json',
|
||||
'portfolio_correlation_gate_json','satellite_failure_gate_json','buy_permission_json'];
|
||||
var missing = required.filter(function(k) { return hApex[k] === undefined; });
|
||||
if (missing.length > 0) {
|
||||
return { ok: false, reason: 'missing:' + missing.join(','),
|
||||
gaps: missing.map(function(k) { return { type: 'HARNESS_KEY_MISSING', item: k }; }) };
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_10: LLM 출력 checksum — 보고서 렌더링 시 검증 (GAS 단계 통과)
|
||||
chk('CV_10', 'LLM 출력 checksum', function() {
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_11: GAS 하네스 키 동기화 — hApex 필수 키 존재 확인 [PROPOSAL47/48: 신규 키 추가]
|
||||
chk('CV_11', 'GAS 하네스 키 동기화', function() {
|
||||
var required = ['buy_permission_json','saqg_json','satellite_failure_gate_json',
|
||||
'data_freshness_json','macro_event_json','predictive_alpha_json','anti_late_entry_json',
|
||||
'watch_breakout_candidates_json','portfolio_alpha_confidence',
|
||||
'anti_whipsaw_reentry_json','alpha_history_summary_json'];
|
||||
var missing = required.filter(function(k) { return hApex[k] === undefined; });
|
||||
if (missing.length > 0) {
|
||||
return { ok: false, reason: 'HARNESS_KEY_MISSING:' + missing.join(','),
|
||||
gaps: missing.map(function(k) { return { type: 'HARNESS_KEY_MISSING', item: k }; }) };
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// CV_12: YAML-to-GAS 커버리지 — PA1~PA4 출력 확인 (자기 자신 consistency_report_json 제외)
|
||||
chk('CV_12', 'YAML-to-GAS 커버리지', function() {
|
||||
var paKeys = ['predictive_alpha_json','anti_late_entry_json',
|
||||
'cash_preservation_sell_json','macro_event_json'];
|
||||
var missing = paKeys.filter(function(k) { return hApex[k] === undefined; });
|
||||
if (missing.length > 0) {
|
||||
return { ok: false, reason: 'GAS_COVERAGE_GAP:' + missing.join(','),
|
||||
gaps: missing.map(function(k) { return { type: 'GAS_COVERAGE_GAP', item: k }; }) };
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
var score = Math.round(passed.length / 12 * 100);
|
||||
var blockStatus = score < 90 ? 'BLOCK' : (score < 100 ? 'WARNING' : 'PASS');
|
||||
|
||||
return {
|
||||
consistency_score: score,
|
||||
cv_verdict: blockStatus,
|
||||
passed: passed,
|
||||
failed: failed,
|
||||
gap_list: gapList,
|
||||
block_status: blockStatus,
|
||||
formula_id: 'CONSISTENCY_VALIDATOR_V2'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---- TASK-001: RELEASE_GATE_TRUTH_V1 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function buildReleaseGateTruthV1_(hApex) {
|
||||
// RC1 수정: honest_proof_score >= 70 이어야만 릴리스 허용
|
||||
// effective_release_gate = AND(cosmetic_gate, honest_gate)
|
||||
var agp = hApex['algorithm_guidance_proof_v1'] || {};
|
||||
var honestScore = agp['honest_proof_score'] || 0;
|
||||
var honestGate = agp['honest_gate'] || 'FAIL';
|
||||
var cosmeticGate = agp['gate'] || 'FAIL';
|
||||
var effectiveGate = (honestGate === 'PASS' && cosmeticGate === 'PASS') ? 'PASS' : 'FAIL';
|
||||
return {
|
||||
formula_id: 'RELEASE_GATE_TRUTH_V1',
|
||||
honest_proof_score: honestScore,
|
||||
honest_gate: honestGate,
|
||||
cosmetic_gate: cosmeticGate,
|
||||
effective_release_gate: effectiveGate,
|
||||
hts_order_mode: honestScore >= 70 ? 'HTS_ALLOWED' : 'THEORETICAL_ONLY',
|
||||
release_blocked_note: honestScore < 70
|
||||
? '[RELEASE_BLOCKED_BY_TRUTH_GATE: honest=' + honestScore + ' < 70]'
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
// ---- TASK-002: NON_VACUOUS_PASS_GUARD_V1 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function guardNonVacuousPass_(gateObj, minSamples) {
|
||||
// RC2 수정: effective_n < minSamples 인 게이트를 WATCH_PENDING_SAMPLE로 강제 강등
|
||||
minSamples = minSamples || 30;
|
||||
var nFields = ['sample_count','row_count','evaluated_count','samples','n','sample_n'];
|
||||
var effectiveN = null;
|
||||
for (var i = 0; i < nFields.length; i++) {
|
||||
if (gateObj[nFields[i]] !== undefined && gateObj[nFields[i]] !== null) {
|
||||
effectiveN = parseInt(gateObj[nFields[i]], 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (effectiveN === null) effectiveN = 0;
|
||||
var gateVal = (gateObj['gate'] || '').toUpperCase();
|
||||
if (effectiveN < minSamples && gateVal === 'PASS') {
|
||||
return {
|
||||
gate: 'WATCH_PENDING_SAMPLE',
|
||||
label: '[PASS_INVALID_LOW_N: n=' + effectiveN + ' < ' + minSamples + ']',
|
||||
vacuous: true
|
||||
};
|
||||
}
|
||||
return { gate: gateVal, vacuous: false };
|
||||
}
|
||||
|
||||
// ---- TASK-004: OPERATIONAL_SAMPLE_BACKFILL_V1 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function evaluateOperationalOutcomeBatch_(proposalHistory, dataFeed, captureDate) {
|
||||
// RC4 수정: LIVE/PAPER 제안의 T+5/T+20 실측 결과를 채움
|
||||
// live=0 상태이므로 현재는 scaffolded — 실측 표본 누적 후 활성화
|
||||
var results = [];
|
||||
var opT5Count = 0;
|
||||
var opT20Count = 0;
|
||||
(proposalHistory || []).forEach(function(p) {
|
||||
if (!p.origin || p.origin === 'REPLAY') return; // REPLAY 제외
|
||||
var today = captureDate ? new Date(captureDate) : new Date();
|
||||
var entryDate = p.entry_date ? new Date(p.entry_date) : null;
|
||||
if (!entryDate) return;
|
||||
var elapsedDays = Math.floor((today - entryDate) / 86400000);
|
||||
var result = { id: p.id, origin: p.origin, entry_date: p.entry_date };
|
||||
if (elapsedDays >= 5 && p.realized_return_pct_t5 === undefined) {
|
||||
result.t5_pending = true; // 실측 미채움
|
||||
} else if (p.realized_return_pct_t5 !== undefined) {
|
||||
opT5Count++;
|
||||
result.t5_filled = true;
|
||||
}
|
||||
if (elapsedDays >= 20 && p.realized_return_pct_t20 === undefined) {
|
||||
result.t20_pending = true;
|
||||
} else if (p.realized_return_pct_t20 !== undefined) {
|
||||
opT20Count++;
|
||||
result.t20_filled = true;
|
||||
}
|
||||
results.push(result);
|
||||
});
|
||||
return {
|
||||
formula_id: 'OPERATIONAL_SAMPLE_BACKFILL_V1',
|
||||
operational_t5_sample_count: opT5Count,
|
||||
operational_t20_sample_count: opT20Count,
|
||||
unvalidated_label: opT5Count < 30 ? '[UNVALIDATED_LIVE: n=' + opT5Count + ' < 30]' : null,
|
||||
results: results
|
||||
};
|
||||
}
|
||||
|
||||
// ---- TASK-005: EVALUATION_WINDOW_HONESTY_V1 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function labelEvaluationWindow_(outcomeQualityJson) {
|
||||
// RC5 수정: t20_source != operational_t20이면 T20_PROXY 플래그
|
||||
var t20Source = (outcomeQualityJson && outcomeQualityJson.t20_source) || null;
|
||||
var isProxy = (t20Source !== 'operational_t20');
|
||||
return {
|
||||
formula_id: 'EVALUATION_WINDOW_HONESTY_V1',
|
||||
t20_source: t20Source,
|
||||
t20_is_proxy: isProxy,
|
||||
t20_label: isProxy ? 'T+20(추정,프록시)' : 'T+20(실측)',
|
||||
release_gate_t20_alpha_blocked: isProxy,
|
||||
proxy_note: isProxy
|
||||
? '[T20_PROXY: t20_source=' + t20Source + ' - 실측 T+20 표본 0건]'
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
// ---- TASK-008: VALUE_PRESERVING_CASH_RAISE_V9 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function calcValuePreservingCashRaiseV9_(sellCandidates, shortfallKrw, regimeLabel) {
|
||||
// RC 수정: BREACH_FULL_LIQUIDATION 금지, K2 50/50 강제
|
||||
var REBOUND_FACTORS = {EVENT_SHOCK:0.7, RISK_OFF:0.6, NEUTRAL:0.5, RISK_ON:0.3};
|
||||
var reboundFactor = REBOUND_FACTORS[regimeLabel] || 0.5;
|
||||
var result = [];
|
||||
var totalDamagePct = 0, count = 0, breachCount = 0;
|
||||
(sellCandidates || []).forEach(function(c) {
|
||||
var qty = parseInt(c.qty || c.quantity || 0, 10);
|
||||
var isOversold = c.rsi14 !== undefined && parseFloat(c.rsi14) < 30;
|
||||
var brtNotBroken = c.brt_verdict !== 'BROKEN';
|
||||
var emergency = !!c.emergency_full_sell;
|
||||
if ((isOversold || brtNotBroken) && !emergency) {
|
||||
// K2 50/50
|
||||
var imm = Math.floor(qty / 2);
|
||||
var wait = qty - imm;
|
||||
var reboundTrigger = parseFloat(c.prev_close || 0) + reboundFactor * parseFloat(c.atr20 || 0);
|
||||
result.push({
|
||||
ticker: c.ticker,
|
||||
immediate_qty: imm,
|
||||
rebound_wait_qty: wait,
|
||||
rebound_trigger_price: Math.round(reboundTrigger),
|
||||
k2_applied: true
|
||||
});
|
||||
} else {
|
||||
if (c.source === 'BREACH_FULL_LIQUIDATION' && !emergency) breachCount++;
|
||||
result.push({ticker: c.ticker, immediate_qty: qty, rebound_wait_qty: 0, k2_applied: false});
|
||||
}
|
||||
totalDamagePct += parseFloat(c.value_damage_pct || 0);
|
||||
count++;
|
||||
});
|
||||
var avgDamage = count > 0 ? totalDamagePct / count : 0;
|
||||
return {
|
||||
formula_id: 'VALUE_PRESERVING_CASH_RAISE_V9',
|
||||
selected_sell_combo: result,
|
||||
raw_value_damage_pct_avg: avgDamage,
|
||||
rebound_capture_probability: result.some(function(r){return r.k2_applied;}) ? 0.5 : 0.0,
|
||||
breach_full_liquidation_count: breachCount,
|
||||
gate: (avgDamage <= 10 && breachCount === 0) ? 'PASS' : 'FAIL'
|
||||
};
|
||||
}
|
||||
|
||||
// ---- TASK-009: CAPITAL_STYLE_ALLOCATION_V2 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function calcCapitalStyleAllocationV2_(ticker, proposalHistory, convictionScore) {
|
||||
// 투자성향별 실측 승률로 가중치 보정 (표본 < 30 시 EXPERT_PRIOR 유지)
|
||||
var styles = ['SCALP','SWING','MOMENTUM','POSITION'];
|
||||
var result = {};
|
||||
styles.forEach(function(style) {
|
||||
var samples = (proposalHistory || []).filter(function(p) {
|
||||
return p.ticker === ticker && p.style === style && p.origin !== 'REPLAY'
|
||||
&& p.realized_return_pct_t5 !== undefined;
|
||||
});
|
||||
var n = samples.length;
|
||||
var wins = samples.filter(function(p){return parseFloat(p.realized_return_pct_t5||0)>0;}).length;
|
||||
result[style] = {
|
||||
sample_n: n,
|
||||
win_rate: n >= 30 ? (wins/n) : null,
|
||||
weight_source: n >= 30 ? 'DYNAMIC' : 'EXPERT_PRIOR',
|
||||
label: n < 30 ? '[UNVALIDATED_WEIGHT: n=' + n + ' < 30]' : null
|
||||
};
|
||||
});
|
||||
// conviction 게이트
|
||||
var recPct = convictionScore < 35 ? 0
|
||||
: convictionScore < 50 ? 1.5
|
||||
: convictionScore < 65 ? 3.0
|
||||
: convictionScore < 80 ? 5.0 : 7.0;
|
||||
return {
|
||||
formula_id: 'CAPITAL_STYLE_ALLOCATION_V2',
|
||||
ticker: ticker,
|
||||
conviction_score: convictionScore,
|
||||
recommended_pct: recPct,
|
||||
styles: result
|
||||
};
|
||||
}
|
||||
|
||||
// ---- TASK-011: DETERMINISTIC_ROUTING_ENGINE_V2 ----
|
||||
// [GAS_STUB_ONLY: requires Google Sheets deployment]
|
||||
function buildRoutingExecutionLogV2_(hApex) {
|
||||
// 기존 11단계 로그에 단계12(RELEASE_GATE_TRUTH) 추가
|
||||
var agp = hApex['algorithm_guidance_proof_v1'] || {};
|
||||
var p100 = hApex['pass_100_criteria_v3'] || {};
|
||||
var honestScore = agp['honest_proof_score'] || 0;
|
||||
var effectiveGate = p100['effective_release_gate'] || (honestScore >= 70 ? 'PASS' : 'FAIL');
|
||||
var step12 = {
|
||||
step: 12,
|
||||
formula_id: 'RELEASE_GATE_TRUTH_V1',
|
||||
label: '릴리스 진실 게이트',
|
||||
status: effectiveGate,
|
||||
honest_proof_score: honestScore,
|
||||
effective_release_gate: effectiveGate,
|
||||
hts_order_count_if_blocked: effectiveGate !== 'PASS' ? 0 : null,
|
||||
blocked_note: effectiveGate !== 'PASS'
|
||||
? '[RELEASE_BLOCKED_BY_TRUTH_GATE: honest=' + honestScore + ' < 70]'
|
||||
: null
|
||||
};
|
||||
// 기존 routing_execution_log에 step12 추가
|
||||
var existing = hApex['routing_execution_log'] || {};
|
||||
var steps = Array.isArray(existing.steps) ? existing.steps.slice() : [];
|
||||
steps.push(step12);
|
||||
return Object.assign({}, existing, {
|
||||
steps: steps,
|
||||
stage_count_target: 12,
|
||||
effective_release_gate: effectiveGate
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// gas_data_collect.gs — compatibility stub (P5-T02 GAS file split)
|
||||
//
|
||||
// 실제 구현은 src/gas_adapter_parts/ 로 이동:
|
||||
// gdc_01_fetch_fundamentals.gs — fetch 인프라·Naver/Yahoo fetchers·펀더멘털·runDataFeed (L1-L2405)
|
||||
// gdc_02_account_satellite.gs — 계좌스냅샷·티커셋업·위성배치·가격맵 (L2406-L4460)
|
||||
//
|
||||
// GAS 프로젝트에 모든 파일을 함께 추가하면 동일한 글로벌 네임스페이스에서 동작합니다.
|
||||
// Ownership: data_feed 팀, QEDD P5-T02
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* gas_data_feed.gs — Google Apps Script 버전 (compatibility stub)
|
||||
*
|
||||
* ⚠️ 이 파일은 P5-T02 GAS 역할 분리 작업의 호환성 스텁입니다.
|
||||
* 실제 함수 구현은 src/gas_adapter_parts/ 아래 분리된 파일로 이동했습니다.
|
||||
*
|
||||
* gdf_01_price_metrics.gs — 가격 지표·RSI·Entry/Exit·점수·매도우선순위 (L1-L2347)
|
||||
* gdf_02_harness_assembly.gs — 하네스 조립·라우팅·레짐·위성 (L2348-L4560)
|
||||
* gdf_03_portfolio_gates.gs — 포트폴리오 게이트·섹터·액션·실행 (L4561-L6806)
|
||||
* gdf_04_execution_quality.gs — 실행품질·Apex·PA1 피드백·매크로 (L6807-L9015)
|
||||
* gdf_05_alpha_engines.gs — 알파엔진·서빙·거래품질·패턴 (L9016-L10302)
|
||||
*
|
||||
* GAS 프로젝트에 모든 파일을 함께 추가하면 동일한 글로벌 네임스페이스에서 동작합니다.
|
||||
*
|
||||
* 배포 방법:
|
||||
* 1. script.google.com → 새 프로젝트
|
||||
* 2. 이 파일 + src/gas_adapter_parts/gdf_*.gs + src/gas_adapter_parts/gdc_*.gs 붙여넣기
|
||||
* 3. 트리거 설정: runDataFeed → 시간 기반 → 매일 → 16:30~17:30
|
||||
*
|
||||
* Ownership: data_feed 팀, QEDD P5-T02 GAS file split
|
||||
*/
|
||||
@@ -0,0 +1,907 @@
|
||||
/**
|
||||
* gas_event_calendar.gs — Market Event Calendar Harness (v2: Yahoo + Naver scrapers)
|
||||
*
|
||||
* 스크래핑 전략:
|
||||
* - Yahoo Finance: __NEXT_DATA__ JSON 추출 (Next.js 내장 데이터)
|
||||
* - Naver Finance: HTML 테이블 파싱 (경제지표 일정 + 실적발표)
|
||||
* - 공통: fetchWithCache_() — CacheService(4h) + 지수 백오프 + stale fallback
|
||||
*
|
||||
* 블록킹 대응:
|
||||
* - Chrome UA + Referer 헤더로 봇 판정 회피
|
||||
* - 429/503 → 재시도, 403/401 → 즉시 stale 사용
|
||||
* - 파싱 실패 시 빈 배열 반환 (에러 전파 없음)
|
||||
*/
|
||||
|
||||
const CFG = {
|
||||
SPREADSHEET_ID: '1e1TNlLfnT69nvw-I1wU_oBHmEtI2pfbld3e0fFmtrZM',
|
||||
SHEET_NAME: 'event_calendar',
|
||||
TIME_ZONE: 'Asia/Seoul',
|
||||
DATE_FORMAT: 'yyyy-MM-dd',
|
||||
ALERT_EMAIL: '',
|
||||
|
||||
REQUIRED_HEADERS: ['Date', 'Event', 'Type', 'Impact', 'Alert'],
|
||||
ALL_HEADERS: ['Date','Event','Type','Impact','Alert','DaysLeft','AlertStatus','LastCheckedAt','Source','SourceUrl','Key'],
|
||||
|
||||
IMPACT_ALERT_WINDOW_DAYS: { HIGH: 7, MEDIUM: 3, LOW: 1 },
|
||||
VALID_TYPES: ['FOMC','US_CPI','US_PPI','US_PCE','US_NFP','EARNINGS','EXPIRY','BOK','KR_CPI','BOJ','FX','BOND','CUSTOM'],
|
||||
VALID_IMPACTS: ['HIGH','MEDIUM','LOW'],
|
||||
|
||||
JSON_SOURCE_PROPERTY: 'EVENT_JSON_URL',
|
||||
CSV_SOURCE_PROPERTY: 'EVENT_CSV_URL',
|
||||
|
||||
// 캐시·재시도
|
||||
CACHE_TTL_SEC: 4 * 60 * 60,
|
||||
STALE_PROP_PREFIX: 'stale_url:',
|
||||
MAX_RETRIES: 2,
|
||||
RETRY_BASE_MS: 1500,
|
||||
|
||||
// 스크래핑
|
||||
YAHOO_DAYS_AHEAD: 60, // Yahoo: 오늘부터 N일 앞까지 수집
|
||||
SCRAPE_SLEEP_MS: 700, // 요청 간 대기 (ms) — rate limit 회피
|
||||
CHROME_UA: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
};
|
||||
|
||||
|
||||
/* ── 메뉴 ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
function onOpen() {
|
||||
SpreadsheetApp.getUi()
|
||||
.createMenu('Market Calendar')
|
||||
.addItem('초기 설정', 'setup')
|
||||
.addItem('검증 및 정렬', 'validateAndSort')
|
||||
.addItem('임박 이벤트 알림 발송', 'sendEventAlerts')
|
||||
.addSeparator()
|
||||
.addItem('Trading Economics 새로고침', 'refreshFromTradingEconomics')
|
||||
.addItem('Naver Finance 새로고침', 'refreshFromNaver')
|
||||
.addItem('외부 URL 소스 새로고침', 'refreshFromSources')
|
||||
.addSeparator()
|
||||
.addItem('프로퍼티 캐시 청소', 'cleanUpProperties')
|
||||
.addItem('샘플 데이터 삽입', 'loadSampleDataIfEmpty')
|
||||
.addItem('매일 실행 트리거 설치', 'createDailyTrigger')
|
||||
.addItem('트리거 삭제', 'deleteProjectTriggers')
|
||||
.addToUi();
|
||||
}
|
||||
|
||||
function setup() {
|
||||
cleanUpProperties(); // 한도 초과 상태 해제를 위해 프로퍼티 캐시 청소 먼저 수행
|
||||
ensureSheetAndHeaders_();
|
||||
validateAndSort();
|
||||
createDailyTrigger();
|
||||
toast_('event_calendar 설정 완료', 5);
|
||||
}
|
||||
|
||||
function runDaily() {
|
||||
// refreshFromTradingEconomics(); // 로컬 수집 방식을 사용하므로 원격 실행은 건너뜁니다.
|
||||
refreshFromNaver();
|
||||
refreshFromSources();
|
||||
validateAndSort();
|
||||
sendEventAlerts();
|
||||
}
|
||||
|
||||
|
||||
/* ── 검증·정렬 ────────────────────────────────────────────────────────────── */
|
||||
|
||||
function validateAndSort() {
|
||||
const sheet = ensureSheetAndHeaders_();
|
||||
const hmap = getHeaderMap_(sheet);
|
||||
const lastRow = sheet.getLastRow();
|
||||
if (lastRow < 2) { toast_('데이터 없음', 3); return; }
|
||||
|
||||
const now = Utilities.formatDate(new Date(), CFG.TIME_ZONE, 'yyyy-MM-dd HH:mm:ss');
|
||||
const today = todayKst_();
|
||||
const range = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn());
|
||||
const values = range.getValues();
|
||||
|
||||
const I = {
|
||||
date: hmap.Date-1, event: hmap.Event-1, type: hmap.Type-1,
|
||||
impact: hmap.Impact-1, days: hmap.DaysLeft-1,
|
||||
status: hmap.AlertStatus-1, checked: hmap.LastCheckedAt-1, key: hmap.Key-1,
|
||||
};
|
||||
|
||||
const rows = values.map(row => {
|
||||
const d = coerceDate_(row[I.date]);
|
||||
const event = String(row[I.event] || '').trim();
|
||||
const type = String(row[I.type] || '').trim().toUpperCase();
|
||||
const impact = String(row[I.impact] || '').trim().toUpperCase();
|
||||
|
||||
const errs = [];
|
||||
if (!d) errs.push('ERROR: invalid date');
|
||||
if (!event) errs.push('ERROR: empty event');
|
||||
if (type && !CFG.VALID_TYPES.includes(type)) errs.push('WARN: unknown type');
|
||||
if (impact && !CFG.VALID_IMPACTS.includes(impact)) errs.push('WARN: unknown impact');
|
||||
|
||||
if (d) { row[I.date] = d; row[I.days] = daysBetween_(today, d); } else row[I.days] = '';
|
||||
if (!row[I.key] && d && event) row[I.key] = buildKey_(d, event, type);
|
||||
if (errs.length) row[I.status] = errs.join(' | ');
|
||||
row[I.checked] = now;
|
||||
row[I.type] = type;
|
||||
row[I.impact] = impact;
|
||||
return row;
|
||||
});
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const da = coerceDate_(a[I.date]), db = coerceDate_(b[I.date]);
|
||||
if (!da && !db) return 0; if (!da) return 1; if (!db) return -1;
|
||||
return da - db;
|
||||
});
|
||||
|
||||
range.setValues(rows);
|
||||
sheet.getRange(2, hmap.Date, Math.max(lastRow-1,1), 1).setNumberFormat(CFG.DATE_FORMAT);
|
||||
applyFormatting_(sheet, hmap);
|
||||
toast_('검증 및 정렬 완료', 3);
|
||||
}
|
||||
|
||||
|
||||
/* ── 이메일 알림 ──────────────────────────────────────────────────────────── */
|
||||
|
||||
function sendEventAlerts() {
|
||||
Logger.log('[sendEventAlerts] 이메일 알림 발송 기능 비활성화 (사용자 요청)');
|
||||
toast_('이메일 알림 발송 건너뜀 (비활성화)', 3);
|
||||
return;
|
||||
|
||||
const todayStr = Utilities.formatDate(new Date(), CFG.TIME_ZONE, CFG.DATE_FORMAT);
|
||||
const props = PropertiesService.getScriptProperties();
|
||||
const due = [];
|
||||
|
||||
rows.forEach(item => {
|
||||
const impact = String(item.Impact || '').toUpperCase();
|
||||
const daysLeft = Number(item.DaysLeft);
|
||||
if (!impact || isNaN(daysLeft) || daysLeft < 0) return;
|
||||
if (daysLeft > (CFG.IMPACT_ALERT_WINDOW_DAYS[impact] || 0)) return;
|
||||
const sentKey = `sent:${todayStr}:${item.Key || buildKey_(coerceDate_(item.Date), item.Event, item.Type)}`;
|
||||
if (props.getProperty(sentKey)) return;
|
||||
due.push({ ...item, DaysLeft: daysLeft, sentKey });
|
||||
});
|
||||
|
||||
if (!due.length) { toast_('오늘 발송할 알림 없음', 3); return; }
|
||||
|
||||
const to = CFG.ALERT_EMAIL || Session.getActiveUser().getEmail();
|
||||
if (!to) throw new Error('ALERT_EMAIL 또는 사용자 이메일 필요');
|
||||
|
||||
MailApp.sendEmail({ to, subject: `[Market Calendar] 임박 이벤트 ${due.length}건`, body: buildEmailBody_(due) });
|
||||
|
||||
due.forEach(item => {
|
||||
props.setProperty(item.sentKey, '1');
|
||||
if (hmap.AlertStatus) sheet.getRange(item.__row, hmap.AlertStatus).setValue(`SENT: ${todayStr}`);
|
||||
});
|
||||
toast_(`알림 발송 완료: ${due.length}건`, 4);
|
||||
}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════ *
|
||||
* Trading Economics 스크래퍼
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
function refreshFromTradingEconomics() {
|
||||
return; // 로컬 수집 방식으로 이관되어 비활성화
|
||||
let sourceName = 'Trading Economics';
|
||||
let events = fetchTradingEconomicsCalendar_(CFG.YAHOO_DAYS_AHEAD);
|
||||
|
||||
if (!events.length) {
|
||||
Logger.log('[TradingEconomics] 차단 또는 결과 없음. Yahoo Finance 오늘 날짜 수집으로 Fallback합니다.');
|
||||
events = fetchYahooCalendar_();
|
||||
sourceName = 'Yahoo Fallback';
|
||||
}
|
||||
|
||||
if (!events.length) {
|
||||
toast_('캘린더: 수집된 이벤트 없음 (야후/TE 모두 차단 또는 일정 없음)', 4);
|
||||
return;
|
||||
}
|
||||
|
||||
upsertEvents_(events);
|
||||
toast_(`${sourceName} 갱신: ${events.length}건`, 4);
|
||||
}
|
||||
|
||||
function fetchTradingEconomicsCalendar_(daysAhead) {
|
||||
Logger.log('[TradingEconomics] 로컬(클라이언트) 수집 방식을 사용하므로 구글 서버의 직접 호출은 건너뜁니다.');
|
||||
return [];
|
||||
|
||||
const today = todayKst_();
|
||||
const startDateStr = Utilities.formatDate(today, CFG.TIME_ZONE, 'yyyy-MM-dd');
|
||||
const end = new Date(today.getFullYear(), today.getMonth(), today.getDate() + (daysAhead || 60));
|
||||
const endDateStr = Utilities.formatDate(end, CFG.TIME_ZONE, 'yyyy-MM-dd');
|
||||
|
||||
const cache = CacheService.getScriptCache();
|
||||
const cacheKey = `te_cal_parsed:${startDateStr}:${endDateStr}`;
|
||||
const cachedData = cache.get(cacheKey);
|
||||
|
||||
if (cachedData !== null) {
|
||||
try {
|
||||
const parsed = JSON.parse(cachedData);
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
} catch(e) {
|
||||
Logger.log(`[TradingEconomics] 캐시 파싱 실패: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const url = "https://tradingeconomics.com/calendar";
|
||||
const headers = {
|
||||
'User-Agent': CFG.CHROME_UA,
|
||||
'Cookie': `cal-custom-range=${startDateStr}|${endDateStr}`
|
||||
};
|
||||
|
||||
let events = [];
|
||||
try {
|
||||
const html = fetchWithCache_(url, CFG.CACHE_TTL_SEC, headers);
|
||||
if (html) {
|
||||
events = parseTradingEconomicsHtml_(html);
|
||||
if (events.length > 0) {
|
||||
cache.put(cacheKey, JSON.stringify(events), 12 * 60 * 60);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
Logger.log(`[TradingEconomics] 실패: ${e.message}`);
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
function fetchYahooCalendar_() {
|
||||
const events = [];
|
||||
const today = todayKst_();
|
||||
const dateStr = Utilities.formatDate(today, CFG.TIME_ZONE, CFG.DATE_FORMAT);
|
||||
|
||||
const cache = CacheService.getScriptCache();
|
||||
const cacheKey = `yahoo_cal_parsed:${dateStr}`;
|
||||
const cachedData = cache.get(cacheKey);
|
||||
|
||||
if (cachedData !== null) {
|
||||
try {
|
||||
const parsed = JSON.parse(cachedData);
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
} catch(e) {
|
||||
Logger.log(`[Yahoo Fallback] 캐시 파싱 실패: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 야후는 day 파라미터가 무시되므로 오늘 날짜 1일치만 fetch합니다.
|
||||
const url = `https://finance.yahoo.com/calendar/economic?day=${dateStr}`;
|
||||
const headers = { 'User-Agent': 'Mozilla/5.0 (compatible; GAS/1.0)' };
|
||||
|
||||
try {
|
||||
const html = fetchWithCache_(url, CFG.CACHE_TTL_SEC, headers);
|
||||
if (html) {
|
||||
const dailyEvents = parseYahooHtml_(html, today);
|
||||
if (dailyEvents.length > 0) {
|
||||
cache.put(cacheKey, JSON.stringify(dailyEvents), 12 * 60 * 60);
|
||||
events.push(...dailyEvents);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
Logger.log(`[Yahoo Fallback] 실패: ${e.message}`);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
function parseYahooHtml_(html, dateHint) {
|
||||
const trMatches = html.match(/<tr[^>]*data-testid="data-table-v2-row"[\s\S]*?<\/tr>/gi);
|
||||
if (!trMatches || !trMatches.length) {
|
||||
Logger.log('[Yahoo Fallback] table row 없음');
|
||||
return [];
|
||||
}
|
||||
|
||||
const events = [];
|
||||
const dateStr = Utilities.formatDate(dateHint, CFG.TIME_ZONE, CFG.DATE_FORMAT);
|
||||
|
||||
for (let i = 0; i < trMatches.length; i++) {
|
||||
const trHtml = trMatches[i];
|
||||
|
||||
const getCell_ = (testId) => {
|
||||
const regex = new RegExp(`data-testid-cell=["']${testId}["'][^>]*>([\\s\\S]*?)</td>`, 'i');
|
||||
const m = trHtml.match(regex);
|
||||
if (m) {
|
||||
let val = m[1].replace(/<[^>]+>/g, ' ');
|
||||
val = val.replace(/\s+/g, ' ').trim();
|
||||
return val;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const eventName = getCell_('econ_release');
|
||||
const country = getCell_('country_code');
|
||||
const actual = getCell_('after_release_actual');
|
||||
const estimate = getCell_('consensus_estimate');
|
||||
const prior = getCell_('prior_release_actual');
|
||||
|
||||
if (!eventName) continue;
|
||||
|
||||
let rawImpact = 'LOW';
|
||||
if (trHtml.toLowerCase().includes('high') || trHtml.toLowerCase().includes('red') || trHtml.toLowerCase().includes('priority-3')) {
|
||||
rawImpact = 'HIGH';
|
||||
} else if (trHtml.toLowerCase().includes('medium') || trHtml.toLowerCase().includes('orange') || trHtml.toLowerCase().includes('priority-2')) {
|
||||
rawImpact = 'MEDIUM';
|
||||
}
|
||||
|
||||
const type = guessEventType_(eventName, country);
|
||||
const finalImpact = guessImpact_(type, eventName) || rawImpact;
|
||||
|
||||
const countryUpper = String(country || '').toUpperCase().trim();
|
||||
const allowedCountries = ['US', 'KR', 'JP'];
|
||||
|
||||
if (!allowedCountries.includes(countryUpper)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type === 'CUSTOM' && finalImpact === 'LOW') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let alertText = '';
|
||||
if (actual && actual !== '-') alertText += `Act: ${actual} `;
|
||||
if (estimate && estimate !== '-') alertText += `Est: ${estimate} `;
|
||||
if (prior && prior !== '-') alertText += `Prev: ${prior}`;
|
||||
alertText = alertText.trim();
|
||||
|
||||
events.push({
|
||||
Date: dateStr,
|
||||
Event: eventName,
|
||||
Type: type,
|
||||
Impact: finalImpact,
|
||||
Alert: alertText,
|
||||
Source: 'Yahoo Finance',
|
||||
SourceUrl: 'https://finance.yahoo.com/calendar/economic',
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
function parseTradingEconomicsHtml_(html) {
|
||||
// data-event가 들어있는 모든 tr 매칭
|
||||
const trMatches = html.match(/<tr[^>]*data-event="[^"]*"[\s\S]*?<\/tr>/gi);
|
||||
if (!trMatches) {
|
||||
Logger.log('[TradingEconomics] event table row 없음');
|
||||
return [];
|
||||
}
|
||||
|
||||
const events = [];
|
||||
|
||||
for (let i = 0; i < trMatches.length; i++) {
|
||||
const trHtml = trMatches[i];
|
||||
|
||||
// td들로 쪼개기
|
||||
const tdMatches = trHtml.match(/<td[^>]*>([\s\S]*?)<\/td>/gi);
|
||||
if (!tdMatches || tdMatches.length < 9) continue;
|
||||
|
||||
// 1) 날짜 추출 (td[0]의 class 속성)
|
||||
const td0 = tdMatches[0];
|
||||
const dateMatch = td0.match(/class=["'](\d{4}-\d{2}-\d{2})["']/i);
|
||||
if (!dateMatch) continue;
|
||||
const dateStr = dateMatch[1];
|
||||
|
||||
// 2) 중요도 추출 (td[0] 내부의 calendar-date-N 클래스)
|
||||
let impact = 'LOW';
|
||||
if (td0.includes('calendar-date-3')) {
|
||||
impact = 'HIGH';
|
||||
} else if (td0.includes('calendar-date-2')) {
|
||||
impact = 'MEDIUM';
|
||||
}
|
||||
|
||||
// 3) 국가 코드 추출 (td[3] 내부 텍스트)
|
||||
const td3 = tdMatches[3];
|
||||
const countryIsoMatch = td3.match(/>([^<]+)</);
|
||||
const countryIso = countryIsoMatch ? countryIsoMatch[1].trim().toUpperCase() : '';
|
||||
|
||||
// 4) 이벤트 이름 추출 (td[4] 내부의 calendar-event 링크 텍스트)
|
||||
const td4 = tdMatches[4];
|
||||
const eventMatch = td4.match(/class=["']calendar-event["'][^>]*>([^<]+)/i);
|
||||
if (!eventMatch) continue;
|
||||
const eventName = eventMatch[1].trim();
|
||||
|
||||
// 5) Actual, Previous, Consensus 값 추출 (HTML 태그 제거 및 공백 정규화)
|
||||
const cleanTdText = (tdHtml) => {
|
||||
let val = tdHtml.replace(/<[^>]+>/g, ' ');
|
||||
val = val.replace(/\s+/g, ' ').trim();
|
||||
return val;
|
||||
};
|
||||
|
||||
const actualVal = cleanTdText(tdMatches[5]);
|
||||
const previousVal = cleanTdText(tdMatches[6]);
|
||||
const consensusVal = cleanTdText(tdMatches[7]);
|
||||
|
||||
// 6) 국가 필터링 (US, KR, JP만 수집)
|
||||
const allowedCountries = ['US', 'KR', 'JP'];
|
||||
if (!allowedCountries.includes(countryIso)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = guessEventType_(eventName, countryIso);
|
||||
const finalImpact = guessImpact_(type, eventName) || impact;
|
||||
|
||||
// 중요도가 LOW이면서 핵심 분류 유형(FOMC 등)이 아닌 일반 CUSTOM 데이터는 제외
|
||||
if (type === 'CUSTOM' && finalImpact === 'LOW') {
|
||||
continue;
|
||||
}
|
||||
// ──────────────────────────
|
||||
|
||||
let alertText = '';
|
||||
if (actualVal && actualVal !== '-') alertText += `Act: ${actualVal} `;
|
||||
if (consensusVal && consensusVal !== '-') alertText += `Est: ${consensusVal} `;
|
||||
if (previousVal && previousVal !== '-') alertText += `Prev: ${previousVal}`;
|
||||
alertText = alertText.trim();
|
||||
|
||||
events.push({
|
||||
Date: dateStr,
|
||||
Event: eventName,
|
||||
Type: type,
|
||||
Impact: finalImpact,
|
||||
Alert: alertText,
|
||||
Source: 'Trading Economics',
|
||||
SourceUrl: 'https://tradingeconomics.com/calendar',
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════ *
|
||||
* Naver Finance 스크래퍼
|
||||
*
|
||||
* 1) 경제지표 일정: https://finance.naver.com/market/news/economic.naver
|
||||
* → 날짜·제목이 포함된 뉴스 목록 테이블 파싱
|
||||
*
|
||||
* 2) 실적 발표: https://finance.naver.com/market/news/announce.naver
|
||||
* → 기업 실적 발표 일정 테이블 파싱
|
||||
*
|
||||
* 블록킹 대응:
|
||||
* - Referer: https://finance.naver.com/ 필수
|
||||
* - Accept-Language: ko-KR 설정
|
||||
* - 429 → fetchWithCache_ 재시도·stale 자동 처리
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
function refreshFromNaver() {
|
||||
const events = fetchNaverCalendar_();
|
||||
if (!events.length) { toast_('Naver: 수집된 이벤트 없음 (차단 또는 일정 없음)', 4); return; }
|
||||
upsertEvents_(events);
|
||||
toast_(`Naver Finance 갱신: ${events.length}건`, 4);
|
||||
}
|
||||
|
||||
function fetchNaverCalendar_() {
|
||||
const headers = {
|
||||
'User-Agent': CFG.CHROME_UA,
|
||||
'Referer': 'https://finance.naver.com/',
|
||||
'Accept-Language': 'ko-KR,ko;q=0.9,en;q=0.8',
|
||||
};
|
||||
|
||||
const events = [];
|
||||
|
||||
// 1. 경제 속보 뉴스 리스트 긁기 (EUC-KR 인코딩)
|
||||
try {
|
||||
const url = 'https://finance.naver.com/news/news_list.naver?mode=LSS2D§ion_id=101§ion_id2=258';
|
||||
const html = fetchWithCache_(url, CFG.CACHE_TTL_SEC, headers, 'EUC-KR');
|
||||
if (html) events.push(...parseNaverNewsList_(html, 'KR', 'Naver 뉴스 속보', url));
|
||||
} catch(e) { Logger.log('[Naver News List] ' + e.message); }
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Naver Finance 뉴스 목록 HTML에서 기사 제목과 발행일을 추출.
|
||||
*/
|
||||
function parseNaverNewsList_(html, region, sourceName, sourceUrl) {
|
||||
const events = [];
|
||||
|
||||
// articleSubject 및 wdate 추출용 정규식
|
||||
const subjectPattern = /<dd class=["']articleSubject["']>[\s\S]*?<a href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
|
||||
const wdatePattern = /<span class=["']wdate["']>([\s\S]*?)<\/span>/i;
|
||||
|
||||
let match;
|
||||
while ((match = subjectPattern.exec(html)) !== null) {
|
||||
const link = match[1].trim();
|
||||
const titleRaw = match[2].trim();
|
||||
|
||||
// HTML 태그 제거 및 공백 정규화
|
||||
const eventName = titleRaw.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
if (eventName.length < 5 || /^\d+$/.test(eventName)) continue;
|
||||
|
||||
// 이 매치 직후 최대 1000자 범위 내에서 가장 가까운 wdate 매칭
|
||||
const pos = subjectPattern.lastIndex;
|
||||
const subHtml = html.substring(pos, pos + 1000);
|
||||
const dateMatch = wdatePattern.exec(subHtml);
|
||||
|
||||
if (dateMatch) {
|
||||
const dateStrRaw = dateMatch[1].trim();
|
||||
const dateOnly = dateStrRaw.split(' ')[0]; // YYYY-MM-DD
|
||||
const eventDate = coerceDate_(dateOnly);
|
||||
|
||||
if (eventDate && eventName) {
|
||||
const type = guessEventType_(eventName, region);
|
||||
events.push({
|
||||
Date: eventDate,
|
||||
Event: eventName,
|
||||
Type: type,
|
||||
Impact: guessImpact_(type, eventName),
|
||||
Alert: '',
|
||||
Source: sourceName,
|
||||
SourceUrl: 'https://finance.naver.com' + link,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
|
||||
/* ── 외부 URL 소스 (기존 유지) ───────────────────────────────────────────── */
|
||||
|
||||
function refreshFromSources() {
|
||||
const props = PropertiesService.getScriptProperties();
|
||||
const jsonUrl = props.getProperty(CFG.JSON_SOURCE_PROPERTY);
|
||||
const csvUrl = props.getProperty(CFG.CSV_SOURCE_PROPERTY);
|
||||
if (!jsonUrl && !csvUrl) { toast_('외부 URL 없음 — Script Properties 확인', 4); return; }
|
||||
|
||||
const events = [];
|
||||
if (jsonUrl) events.push(...fetchEventsFromJson_(jsonUrl));
|
||||
if (csvUrl) events.push(...fetchEventsFromCsv_(csvUrl));
|
||||
if (!events.length) { toast_('외부 소스 이벤트 없음', 3); return; }
|
||||
|
||||
upsertEvents_(events);
|
||||
validateAndSort();
|
||||
toast_(`외부 이벤트 갱신: ${events.length}건`, 5);
|
||||
}
|
||||
|
||||
function fetchEventsFromJson_(url) {
|
||||
const text = fetchWithCache_(url);
|
||||
if (!text) return [];
|
||||
const parsed = JSON.parse(text);
|
||||
if (!Array.isArray(parsed)) throw new Error('JSON source는 배열이어야 합니다.');
|
||||
return parsed.map(normalizeEvent_);
|
||||
}
|
||||
|
||||
function fetchEventsFromCsv_(url) {
|
||||
const text = fetchWithCache_(url);
|
||||
if (!text) return [];
|
||||
const csv = Utilities.parseCsv(text);
|
||||
if (csv.length < 2) return [];
|
||||
const headers = csv[0].map(h => String(h || '').trim());
|
||||
return csv.slice(1).map(row => {
|
||||
const obj = {};
|
||||
headers.forEach((h, i) => { obj[h] = row[i]; });
|
||||
return normalizeEvent_(obj);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════ *
|
||||
* fetchWithCache_ — CacheService + 재시도 + stale fallback
|
||||
*
|
||||
* signature: fetchWithCache_(url, ttlSec?, extraHeaders?, encoding?)
|
||||
* - ttlSec: 캐시 유효기간 (기본 CFG.CACHE_TTL_SEC)
|
||||
* - extraHeaders: 추가 HTTP 헤더 (스크래핑 시 UA/Referer 주입용)
|
||||
* - encoding: 응답 문자셋 인코딩 (기본 'UTF-8')
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
function fetchWithCache_(url, ttlSec, extraHeaders, encoding) {
|
||||
const cache = CacheService.getScriptCache();
|
||||
const cacheKey = 'url:' + md5_(url);
|
||||
|
||||
// 1. Cache HIT
|
||||
const hit = cache.get(cacheKey);
|
||||
if (hit !== null) return hit;
|
||||
|
||||
// 2. Fetch with retry
|
||||
const opts = {
|
||||
muteHttpExceptions: true,
|
||||
followRedirects: true,
|
||||
headers: Object.assign({ 'User-Agent': CFG.CHROME_UA }, extraHeaders || {}),
|
||||
};
|
||||
|
||||
const charset = encoding || 'UTF-8';
|
||||
|
||||
for (let attempt = 0; attempt <= CFG.MAX_RETRIES; attempt++) {
|
||||
if (attempt > 0) Utilities.sleep(CFG.RETRY_BASE_MS * attempt);
|
||||
let resp;
|
||||
try { resp = UrlFetchApp.fetch(url, opts); }
|
||||
catch(e) { Logger.log(`[fetch] 예외 (${attempt}): ${e.message}`); continue; }
|
||||
|
||||
const code = resp.getResponseCode();
|
||||
if (code === 429 || code === 503) { Utilities.sleep(2500 * (attempt+1)); continue; } // 일시 블록
|
||||
if (code === 403 || code === 401) { Logger.log(`[fetch] ${code} 영구 블록: ${url}`); break; }
|
||||
if (code < 200 || code >= 300) { Logger.log(`[fetch] HTTP ${code} (${attempt}): ${url}`); continue; }
|
||||
|
||||
const text = resp.getContentText(charset);
|
||||
try {
|
||||
cache.put(cacheKey, text, ttlSec || CFG.CACHE_TTL_SEC);
|
||||
} catch(e) {
|
||||
// 100KB 초과 HTML은 캐싱 크기 제한으로 실패하는 것이 정상이므로 로그 남기지 않고 패스
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
Logger.log(`[fetch] 실패: ${url}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* ── Upsert / Sample ─────────────────────────────────────────────────────── */
|
||||
|
||||
function upsertEvents_(events) {
|
||||
if (!events.length) return;
|
||||
const sheet = ensureSheetAndHeaders_();
|
||||
const hmap = getHeaderMap_(sheet);
|
||||
const rowByKey = {};
|
||||
getDataObjects_(sheet, hmap).forEach(item => { if (item.Key) rowByKey[item.Key] = item.__row; });
|
||||
|
||||
events.forEach(ev => {
|
||||
const d = coerceDate_(ev.Date);
|
||||
if (!d || !ev.Event) return;
|
||||
const type = String(ev.Type || 'CUSTOM').toUpperCase();
|
||||
const key = ev.Key || buildKey_(d, ev.Event, type);
|
||||
const vals = { Date:d, Event:ev.Event, Type:type, Impact:String(ev.Impact||'MEDIUM').toUpperCase(),
|
||||
Alert:ev.Alert||'', Source:ev.Source||'', SourceUrl:ev.SourceUrl||'', Key:key };
|
||||
|
||||
if (rowByKey[key]) {
|
||||
Object.keys(vals).forEach(h => { if (hmap[h]) sheet.getRange(rowByKey[key], hmap[h]).setValue(vals[h]); });
|
||||
} else {
|
||||
const row = new Array(sheet.getLastColumn()).fill('');
|
||||
Object.keys(vals).forEach(h => { if (hmap[h]) row[hmap[h]-1] = vals[h]; });
|
||||
sheet.appendRow(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadSampleDataIfEmpty() {
|
||||
const sheet = ensureSheetAndHeaders_();
|
||||
if (sheet.getLastRow() > 1) { toast_('이미 데이터 있음 — 삽입 생략', 4); return; }
|
||||
sheet.getRange(2,1,6,5).setValues([
|
||||
['2026-06-17','FOMC 금리결정','FOMC','HIGH','금리동결 시 KOSPI +1~2% 기대'],
|
||||
['2026-07-28','FOMC 금리결정','FOMC','HIGH',''],
|
||||
['2026-06-11','미국 CPI (5월)','US_CPI','HIGH','예상치 상회 시 당일 신규매수 자제'],
|
||||
['2026-07-15','미국 CPI (6월)','US_CPI','HIGH','FOMC 전 마지막 CPI'],
|
||||
['2026-06-20','삼성전자 1Q 잠정실적','EARNINGS','HIGH','반도체 섹터 선행 지표'],
|
||||
['2026-06-15','옵션만기일','EXPIRY','MEDIUM','변동성 확대 구간 주의'],
|
||||
]);
|
||||
validateAndSort();
|
||||
}
|
||||
|
||||
|
||||
/* ── 트리거 ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
function createDailyTrigger() {
|
||||
const fn = 'runDaily';
|
||||
ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() === fn).forEach(t => ScriptApp.deleteTrigger(t));
|
||||
ScriptApp.newTrigger(fn).timeBased().everyDays(1).atHour(8).create();
|
||||
toast_('매일 오전 8시 트리거 설치 완료', 4);
|
||||
}
|
||||
|
||||
function deleteProjectTriggers() {
|
||||
ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
|
||||
toast_('트리거 삭제 완료', 4);
|
||||
}
|
||||
|
||||
function setJsonSourceUrl() { _saveUrlProp_(CFG.JSON_SOURCE_PROPERTY, 'EVENT_JSON_URL'); }
|
||||
function setCsvSourceUrl() { _saveUrlProp_(CFG.CSV_SOURCE_PROPERTY, 'EVENT_CSV_URL'); }
|
||||
function _saveUrlProp_(k, label) {
|
||||
const v = Browser.inputBox(label + '를 입력하세요.');
|
||||
if (v && v !== 'cancel') PropertiesService.getScriptProperties().setProperty(k, v);
|
||||
}
|
||||
|
||||
|
||||
/* ── 이벤트 타입·임팩트 추론 헬퍼 ───────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* 이벤트 이름으로 타입을 추론.
|
||||
* region: 'US' | 'KR' (기본 'US')
|
||||
*/
|
||||
const TYPE_MAP_ = [
|
||||
{ keys: ['FOMC','연준','Federal Open Market','Fed Rate'], type: 'FOMC' },
|
||||
{ keys: ['CPI','소비자물가','Consumer Price'], type: null }, // region 분기
|
||||
{ keys: ['PPI','생산자물가','Producer Price'], type: 'US_PPI' },
|
||||
{ keys: ['PCE','개인소비지출','Personal Consumption'], type: 'US_PCE' },
|
||||
{ keys: ['NFP','비농업','Nonfarm','Payroll'], type: 'US_NFP' },
|
||||
{ keys: ['실적','잠정실적','Earnings','EPS','Revenue'], type: 'EARNINGS' },
|
||||
{ keys: ['옵션만기','선물만기','만기일','Expiry','Triple Witching'], type: 'EXPIRY' },
|
||||
{ keys: ['한국은행','금통위','BOK','Bank of Korea'], type: 'BOK' },
|
||||
{ keys: ['환율','FX','Dollar','달러'], type: 'FX' },
|
||||
{ keys: ['국채','채권','Bond','Treasury'], type: 'BOND' },
|
||||
{ keys: ['BOJ','일본은행','Bank of Japan','BOJ Rate','BOJ Interest'], type: 'BOJ' },
|
||||
];
|
||||
|
||||
function guessEventType_(eventName, region) {
|
||||
const upper = String(eventName || '').toUpperCase();
|
||||
const reg = String(region || '').toUpperCase().trim();
|
||||
|
||||
for (const rule of TYPE_MAP_) {
|
||||
if (rule.keys.some(k => upper.includes(k.toUpperCase()))) {
|
||||
if (rule.type === null) {
|
||||
// CPI 분기: 한국 CPI vs 미국 CPI (타국 CPI는 CUSTOM 처리하여 오인 방지)
|
||||
if (reg === 'KR' || upper.includes('한국') || upper.includes('KR')) return 'KR_CPI';
|
||||
if (reg === 'US' || upper.includes('미국') || upper.includes('US')) return 'US_CPI';
|
||||
return 'CUSTOM';
|
||||
}
|
||||
|
||||
// PPI, PCE, NFP, FOMC 등 미국 전용 타입들은 국가 코드가 US인 경우에만 해당 타입 할당, 타국은 CUSTOM 처리
|
||||
const usOnlyTypes = ['US_PPI', 'US_PCE', 'US_NFP', 'FOMC'];
|
||||
if (usOnlyTypes.includes(rule.type) && reg !== 'US' && reg !== '') {
|
||||
return 'CUSTOM';
|
||||
}
|
||||
|
||||
// BOJ 일본은행 전용 타입은 국가 코드가 JP인 경우에만 해당 타입 할당, 타국은 CUSTOM 처리
|
||||
if (rule.type === 'BOJ' && reg !== 'JP' && reg !== '') {
|
||||
return 'CUSTOM';
|
||||
}
|
||||
|
||||
return rule.type;
|
||||
}
|
||||
}
|
||||
return 'CUSTOM';
|
||||
}
|
||||
|
||||
/** 타입 기반 기본 임팩트 */
|
||||
function guessImpact_(type, eventName) {
|
||||
const highTypes = ['FOMC','US_CPI','US_NFP','BOK','KR_CPI','BOJ'];
|
||||
const medTypes = ['US_PPI','US_PCE','EARNINGS','EXPIRY'];
|
||||
if (highTypes.includes(type)) return 'HIGH';
|
||||
if (medTypes.includes(type)) return 'MEDIUM';
|
||||
return 'LOW';
|
||||
}
|
||||
|
||||
|
||||
/* ── 내부 헬퍼 (compact) ─────────────────────────────────────────────────── */
|
||||
|
||||
function safeGet_(obj, keys) {
|
||||
return keys.reduce((o, k) => (o && o[k] !== undefined ? o[k] : null), obj);
|
||||
}
|
||||
|
||||
function getSpreadsheet_() {
|
||||
return CFG.SPREADSHEET_ID ? SpreadsheetApp.openById(CFG.SPREADSHEET_ID) : SpreadsheetApp.getActiveSpreadsheet();
|
||||
}
|
||||
|
||||
function ensureSheetAndHeaders_() {
|
||||
const ss = getSpreadsheet_();
|
||||
const sheet = ss.getSheetByName(CFG.SHEET_NAME) || ss.insertSheet(CFG.SHEET_NAME);
|
||||
const lastCol = Math.max(sheet.getLastColumn(), 1);
|
||||
const existing = sheet.getRange(1,1,1,lastCol).getValues()[0].map(h => String(h||'').trim());
|
||||
if (!existing.some(Boolean)) {
|
||||
sheet.getRange(1,1,1,CFG.ALL_HEADERS.length).setValues([CFG.ALL_HEADERS]);
|
||||
return sheet;
|
||||
}
|
||||
const missing = CFG.ALL_HEADERS.filter(h => !existing.includes(h));
|
||||
if (missing.length) sheet.getRange(1, sheet.getLastColumn()+1, 1, missing.length).setValues([missing]);
|
||||
const hmap = getHeaderMap_(sheet);
|
||||
CFG.REQUIRED_HEADERS.forEach(h => { if (!hmap[h]) throw new Error(`필수 헤더 없음: ${h}`); });
|
||||
return sheet;
|
||||
}
|
||||
|
||||
function getHeaderMap_(sheet) {
|
||||
const map = {};
|
||||
sheet.getRange(1,1,1,sheet.getLastColumn()).getValues()[0]
|
||||
.forEach((h,i) => { const k=String(h||'').trim(); if(k) map[k]=i+1; });
|
||||
return map;
|
||||
}
|
||||
|
||||
function getDataObjects_(sheet, hmap) {
|
||||
const lastRow = sheet.getLastRow();
|
||||
if (lastRow < 2) return [];
|
||||
const headers = Object.keys(hmap);
|
||||
const lastCol = sheet.getLastColumn();
|
||||
return sheet.getRange(2,1,lastRow-1,lastCol).getValues().map((row,r) => {
|
||||
const obj = { __row: r+2 };
|
||||
headers.forEach(h => { obj[h] = row[hmap[h]-1]; });
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeEvent_(obj) {
|
||||
return {
|
||||
Date: obj.Date || obj.date,
|
||||
Event: obj.Event || obj.event || obj.title || obj.name,
|
||||
Type: obj.Type || obj.type || 'CUSTOM',
|
||||
Impact: obj.Impact || obj.impact || 'MEDIUM',
|
||||
Alert: obj.Alert || obj.alert || '',
|
||||
Source: obj.Source || obj.source || '',
|
||||
SourceUrl: obj.SourceUrl || obj.sourceUrl || obj.url || '',
|
||||
Key: obj.Key || obj.key || '',
|
||||
};
|
||||
}
|
||||
|
||||
function coerceDate_(v) {
|
||||
if (v instanceof Date && !isNaN(v)) return new Date(v.getFullYear(), v.getMonth(), v.getDate());
|
||||
if (typeof v === 'string') {
|
||||
const m = v.trim().match(/^(\d{4})[-./](\d{1,2})[-./](\d{1,2})/);
|
||||
if (m) return new Date(+m[1], +m[2]-1, +m[3]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function todayKst_() {
|
||||
return coerceDate_(Utilities.formatDate(new Date(), CFG.TIME_ZONE, CFG.DATE_FORMAT));
|
||||
}
|
||||
|
||||
function daysBetween_(a, b) {
|
||||
return Math.round(
|
||||
(new Date(b.getFullYear(),b.getMonth(),b.getDate()) -
|
||||
new Date(a.getFullYear(),a.getMonth(),a.getDate())) / 86400000
|
||||
);
|
||||
}
|
||||
|
||||
function buildKey_(dateObj, eventName, type) {
|
||||
return md5_([Utilities.formatDate(dateObj,CFG.TIME_ZONE,CFG.DATE_FORMAT),
|
||||
String(type||'').toUpperCase(), String(eventName||'').trim()].join('|'));
|
||||
}
|
||||
|
||||
function md5_(text) {
|
||||
return Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, text, Utilities.Charset.UTF_8)
|
||||
.map(b => ('0'+(b<0?b+256:b).toString(16)).slice(-2)).join('');
|
||||
}
|
||||
|
||||
function buildEmailBody_(events) {
|
||||
const fmt = d => d instanceof Date ? Utilities.formatDate(d,CFG.TIME_ZONE,CFG.DATE_FORMAT) : String(d);
|
||||
return [
|
||||
'시장 이벤트 임박 알림','',
|
||||
'기준: '+Utilities.formatDate(new Date(),CFG.TIME_ZONE,'yyyy-MM-dd HH:mm:ss'),'',
|
||||
...events.flatMap((item,i) => [
|
||||
`${i+1}. [${item.Impact}] ${fmt(item.Date)} / D-${item.DaysLeft}`,
|
||||
` Event: ${item.Event}`, ` Type: ${item.Type}`,
|
||||
...(item.Alert?[` Alert: ${item.Alert}`]:[]),'',
|
||||
]),
|
||||
'이 알림은 자동 알림이며 투자 판단의 최종 근거가 아닙니다.',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function applyFormatting_(sheet, hmap) {
|
||||
const lastRow = Math.max(sheet.getLastRow(),1), lastCol = Math.max(sheet.getLastColumn(),1);
|
||||
sheet.getRange(1,1,1,lastCol).setFontWeight('bold');
|
||||
sheet.setFrozenRows(1);
|
||||
for (let c=1;c<=lastCol;c++) sheet.autoResizeColumn(c);
|
||||
if (lastRow >= 2) {
|
||||
if (hmap.Impact) sheet.getRange(2,hmap.Impact, lastRow-1,1).setFontWeight('bold');
|
||||
if (hmap.DaysLeft) sheet.getRange(2,hmap.DaysLeft,lastRow-1,1).setNumberFormat('0');
|
||||
}
|
||||
}
|
||||
|
||||
function toast_(msg, sec) {
|
||||
try {
|
||||
const activeSs = SpreadsheetApp.getActive();
|
||||
if (activeSs) {
|
||||
activeSs.toast(msg, 'Market Calendar', sec);
|
||||
} else {
|
||||
Logger.log('[TOAST] ' + msg);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.log('[TOAST] ' + msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 용량을 극도로 많이 소모하는 Script Properties의 캐시성 데이터(stale_url, cal_parsed 등)를 청소.
|
||||
* SPREADSHEET_ID 나 sf_w2_ranks_json 같은 중요 설정/운영 데이터는 보호합니다.
|
||||
*/
|
||||
function cleanUpProperties() {
|
||||
const props = PropertiesService.getScriptProperties();
|
||||
const keys = props.getKeys();
|
||||
let deleteCount = 0;
|
||||
|
||||
// SPREADSHEET_ID, sf_w2_ranks_json, EVENT_JSON_URL, EVENT_CSV_URL, HARNESS_VERBOSE_LOG 등 설정은 제외
|
||||
const protectedKeys = ['SPREADSHEET_ID', 'sf_w2_ranks_json', 'EVENT_JSON_URL', 'EVENT_CSV_URL', 'HARNESS_VERBOSE_LOG'];
|
||||
|
||||
keys.forEach(k => {
|
||||
if (protectedKeys.includes(k)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 캐시 관련 접두사를 가진 항목 및 임시 런타임 상태값 삭제
|
||||
const shouldDelete =
|
||||
k.indexOf('stale_url:') === 0 ||
|
||||
k.indexOf('yahoo_cal_parsed:') === 0 ||
|
||||
k.indexOf('te_cal_parsed:') === 0 ||
|
||||
k.indexOf('url:') === 0 ||
|
||||
k.indexOf('fetch_budget_') === 0 ||
|
||||
k.indexOf('fetch_fail_') === 0 ||
|
||||
k.indexOf('fetch_circuit_') === 0 ||
|
||||
k.indexOf('fetch_session_') === 0 ||
|
||||
k.indexOf('cs_') === 0;
|
||||
|
||||
if (shouldDelete) {
|
||||
props.deleteProperty(k);
|
||||
deleteCount++;
|
||||
}
|
||||
});
|
||||
|
||||
toast_(`프로퍼티 캐시 청소 완료: ${deleteCount}건 삭제`, 5);
|
||||
}
|
||||
+1456
File diff suppressed because it is too large
Load Diff
+2964
File diff suppressed because it is too large
Load Diff
+446
@@ -0,0 +1,446 @@
|
||||
// gas_report.gs - Report & template generation
|
||||
// getDailyBrief, getSummaryJson, getTradeTemplate
|
||||
// Changes only when report format changes. Rarely touched during engine work.
|
||||
// GAS global scope: functions in gas_lib.gs / gas_data_feed.gs callable directly
|
||||
|
||||
|
||||
// ── E1: 일일 의사결정 브리핑 ─────────────────────────────────────────────────
|
||||
// 시장 상태·포트폴리오 건강·액션 목록·주의 종목·7일 이벤트를 한 JSON으로 통합.
|
||||
// doGet(?view=brief) 또는 cacheAllViews()에서 매일 1회 생성.
|
||||
function getDailyBrief(sellPriorityViewInput) {
|
||||
const macro = getMacroJson();
|
||||
const settings = readSettingsTab_();
|
||||
const port = getPortfolioJson();
|
||||
const events = getEventRiskJson();
|
||||
const today = Utilities.formatDate(new Date(), "Asia/Seoul", "yyyy-MM-dd");
|
||||
const holdings = port.holdings ?? [];
|
||||
|
||||
// ── 액션 분류: Final_Action canonical 기준 (A-1/B-1 — Allowed_Action 기반 제거) ──
|
||||
// Final_Action이 canonical output field. Allowed_Action은 중간 계산값.
|
||||
const BUY_FINALS_ = new Set(["BUY_STAGE1_READY","BUY_BREAKOUT_PILOT_ONLY","BUY_PULLBACK_WAIT"]);
|
||||
const SELL_FINALS_ = new Set(["SELL_READY"]);
|
||||
const EXIT_FINALS_ = new Set(["EXIT_SIGNAL","EXIT_REVIEW"]);
|
||||
|
||||
const sellList = holdings.filter(h => SELL_FINALS_.has(h.Final_Action));
|
||||
const exitList = holdings.filter(h => EXIT_FINALS_.has(h.Final_Action));
|
||||
const buyList = holdings.filter(h => BUY_FINALS_.has(h.Final_Action));
|
||||
const watchList = holdings.filter(h => h.Final_Action === "WATCH_TIMING_SETUP");
|
||||
const holdList = holdings.filter(h =>
|
||||
!SELL_FINALS_.has(h.Final_Action) && !EXIT_FINALS_.has(h.Final_Action) &&
|
||||
!BUY_FINALS_.has(h.Final_Action) && h.Final_Action !== "WATCH_TIMING_SETUP"
|
||||
);
|
||||
|
||||
// 주의 종목
|
||||
const stage2Pass = holdings.filter(h => h.Stage2_Gate === "PASS");
|
||||
const timeStopNear= holdings.filter(h => Number.isFinite(+h.Days_To_Time_Stop)
|
||||
&& +h.Days_To_Time_Stop >= 0
|
||||
&& +h.Days_To_Time_Stop <= 7);
|
||||
const overweight = holdings.filter(h => h.Band_Status === "OVERWEIGHT");
|
||||
const tp1Near = holdings.filter(h => Number.isFinite(+h.Profit_Pct) && +h.Profit_Pct >= 10);
|
||||
|
||||
// 포트폴리오 건강 판단
|
||||
const heatVal = parseFloat(macro.total_heat_pct);
|
||||
const fcVal = parseFloat(macro.fc_budget_pct);
|
||||
const heatOk = Number.isFinite(heatVal) && heatVal < 10;
|
||||
const heatCautionB= Number.isFinite(heatVal) && heatVal >= 7 && heatVal < 10;
|
||||
const heatBlockB = Number.isFinite(heatVal) && heatVal >= 10;
|
||||
const fcOk = Number.isFinite(fcVal) && fcVal < 100;
|
||||
const regimeStr = String(macro.market_regime ?? "");
|
||||
const isRiskOffB = regimeStr === "RISK_OFF" || regimeStr === "RISK_OFF_CANDIDATE";
|
||||
const nrf = macro.net_return_feedback;
|
||||
const orbitAdj= parseInt(macro.orbit_slot_adj) || 0;
|
||||
|
||||
// account_snapshot freshness 체크
|
||||
const acctFresh = checkAccountSnapshotFreshness_();
|
||||
|
||||
// 텍스트 브리핑 (ChatGPT 직접 복붙용)
|
||||
const L = [];
|
||||
const hardBlockWarn = String(settings["cash_floor_hard_block_warning"] ?? "").trim();
|
||||
const accountConfirmWarn = String(settings["account_snapshot_confirmation_warning"] ?? "").trim();
|
||||
const cashLedgerWarn = String(settings["cash_ledger_warning"] ?? "").trim();
|
||||
if (hardBlockWarn) L.push(`[긴급 경고] ${hardBlockWarn}`);
|
||||
if (accountConfirmWarn) L.push(`[운영 경고] ${accountConfirmWarn}`);
|
||||
if (cashLedgerWarn) L.push(`[운영 경고] ${cashLedgerWarn}`);
|
||||
L.push(`[시장] ${macro.market_regime} / MRS ${macro.mrs_score}/10 / VIX ${macro.vix} / KOSPI ${macro.kospi} / USD/KRW ${macro.usd_krw}`);
|
||||
const heatTag = heatBlockB ? "⚠HF005:BLOCK" : heatCautionB ? "⚠CAUTION:수량50%감액" : "OK";
|
||||
L.push(`[포트폴리오] HEAT ${macro.total_heat_pct}%(${heatTag}) / FC ${macro.fc_budget_pct}%(${fcOk?"OK":"⚠EXHAUSTED"}) / ${nrf} / BUCKET ${macro.bucket_status}`);
|
||||
if (isRiskOffB) L.push(`[⚠ 레짐 차단] ${regimeStr} — 신규 매수 전면 차단, 보유 종목 50% 단계 축소 검토`);
|
||||
const bayesSourceTag = macro.bayesian_data_source === "actual" ? "실제거래기반" : "기본값(거래이력없음)";
|
||||
L.push(`[Bayesian] ${macro.bayesian_label} (${macro.bayesian_multiplier}×) — ${bayesSourceTag}`);
|
||||
if (acctFresh.fresh === false) L.push(`[⚠ account_snapshot STALE] ${acctFresh.reason} — 손절가·수량 재확인 필요`);
|
||||
else if (acctFresh.fresh === null) L.push(`[⚠ account_snapshot] ${acctFresh.reason}`);
|
||||
|
||||
// 데이터 신선도 경고 — PRICE_STALE / PRICE_QUOTE_ONLY / FLOW_STALE
|
||||
const priceStaleList_ = holdings.filter(h => h.Price_Status === "PRICE_STALE");
|
||||
const quoteOnlyList_ = holdings.filter(h => h.Price_Status === "PRICE_QUOTE_ONLY");
|
||||
const flowStaleList_ = holdings.filter(h => String(h.Missing_Fields ?? "").includes("FLOW_STALE"));
|
||||
if (priceStaleList_.length)
|
||||
L.push(`[⚠ 가격 스테일] ${priceStaleList_.map(h => h.Name).join(", ")} — OHLC 날짜 오래됨, runDataFeed 재실행 권장`);
|
||||
if (quoteOnlyList_.length)
|
||||
L.push(`[⚠ 호가전용] ${quoteOnlyList_.map(h => h.Name).join(", ")} — OHLC 수집 실패, MA/ATR 결측 → OBSERVE_ONLY 처리`);
|
||||
if (flowStaleList_.length)
|
||||
L.push(`[⚠ 수급 스테일] ${flowStaleList_.map(h => h.Name).join(", ")} — 외국인/기관 수급 날짜 오래됨`);
|
||||
|
||||
if (orbitAdj !== 0)
|
||||
L.push(`[Orbit] ${macro.orbit_state} → 공격슬롯 ${orbitAdj>0?"+":""}${orbitAdj}개 / 현금조정 ${macro.orbit_cash_adj}%p`);
|
||||
// ── C-1: Final_Action 기준 단일 우선순위 목록 ─────────────────────────────
|
||||
// 우선순위 순서: SELL_READY > EXIT_* > BUY > WATCH > HOLD
|
||||
// 같은 그룹 내에서는 Final_Rank(Priority_Score) 오름차순
|
||||
const byRank = (arr) => [...arr].sort((a, b) => (+a.Final_Rank || 999) - (+b.Final_Rank || 999));
|
||||
|
||||
L.push("─".repeat(44));
|
||||
L.push(`[오늘 액션] — ${today} (Final_Action 기준, 우선순위 정렬)`);
|
||||
|
||||
if (sellList.length) {
|
||||
L.push(" ▶ SELL_READY (즉시 HTS 주문 가능)");
|
||||
byRank(sellList).forEach((h, i) => {
|
||||
const r = h.Action_Reason || `${h.Sell_Action} ${h.Sell_Qty}주 @${h.Sell_Limit_Price}`;
|
||||
const p = h.Action_Params ? `\n ${h.Action_Params}` : "";
|
||||
L.push(` ${i+1}. ${h.Name} → ${r}${p}`);
|
||||
});
|
||||
}
|
||||
if (exitList.length) {
|
||||
L.push(" ▶ EXIT_SIGNAL / REVIEW (캡처 → ChatGPT 수량 계산 후 매도)");
|
||||
byRank(exitList).forEach((h, i) => {
|
||||
const r = h.Action_Reason || `${h.Final_Action}(RW${h.RW_Partial})`;
|
||||
const p = h.Action_Params ? ` | ${h.Action_Params}` : "";
|
||||
L.push(` ${sellList.length+i+1}. ${h.Name}[${h.Final_Action}] → ${r}${p}`);
|
||||
});
|
||||
}
|
||||
if (buyList.length) {
|
||||
L.push(" ▶ BUY (진입 조건 충족)");
|
||||
byRank(buyList).forEach((h, i) => {
|
||||
const constr = h.Pos_Size_Constraint || "미계산*";
|
||||
const rank_ = sellList.length + exitList.length + i + 1;
|
||||
L.push(` ${rank_}. ${h.Name}[${h.Final_Action}] → ${h.Action_Reason || ""}`);
|
||||
const params_ = h.Action_Params || `목표 ${h.Pos_Size_Qty}주[${constr}]`;
|
||||
L.push(` ${params_}`);
|
||||
});
|
||||
}
|
||||
if (watchList.length) {
|
||||
L.push(" ▶ WATCH (타이밍 대기)");
|
||||
byRank(watchList).forEach((h, i) => {
|
||||
const rank_ = sellList.length + exitList.length + buyList.length + i + 1;
|
||||
L.push(` ${rank_}. ${h.Name} → ${h.Action_Reason || `SS001:${h.SS001_Grade} 타이밍미충족`}`);
|
||||
});
|
||||
}
|
||||
if (holdList.length) {
|
||||
L.push(" ▶ HOLD / BLOCK");
|
||||
byRank(holdList).forEach((h, i) => {
|
||||
const rank_ = sellList.length + exitList.length + buyList.length + watchList.length + i + 1;
|
||||
L.push(` ${rank_}. ${h.Name}[${h.Allowed_Action}] → ${h.Action_Reason || h.Allowed_Action}`);
|
||||
});
|
||||
}
|
||||
if (!sellList.length && !exitList.length && !buyList.length && !watchList.length)
|
||||
L.push(" HOLD — 오늘 액션 없음");
|
||||
|
||||
// 단일 진실원천: sell_priority는 반드시 runSellPriority() 결과만 사용
|
||||
const sellPriorityView_ = sellPriorityViewInput || runSellPriority();
|
||||
const _cashRaiseCands_ = Array.isArray(sellPriorityView_.sell_priority_table)
|
||||
? sellPriorityView_.sell_priority_table
|
||||
: [];
|
||||
|
||||
const _cashBelowTgt_ = isRiskOffB || (() => {
|
||||
const cp = parseFloat(macro.immediate_cash_pct ?? macro.cash_pct ?? "");
|
||||
const tp = parseFloat(macro.target_cash_pct ?? settings["weekly_target_cash_pct"] ?? "10");
|
||||
return Number.isFinite(cp) && Number.isFinite(tp) && cp < tp;
|
||||
})();
|
||||
|
||||
if (_cashBelowTgt_ && _cashRaiseCands_.length) {
|
||||
L.push("─".repeat(44));
|
||||
const gapReason = isRiskOffB
|
||||
? `REGIME_TRIM_50 발동(${regimeStr})`
|
||||
: `현금 부족 → sell_priority_engine`;
|
||||
L.push(`[현금확보 매도우선순위] — ${gapReason}`);
|
||||
L.push(" spec: ①하드스탑>②매도신호>③중복ETF>④손실위성>⑥익절>⑨코어주도주(마지막)");
|
||||
L.push(" ⚠ 매도수량은 HTS 캡처 제공 후 결정 — 수량 미제공 시 수량 산출 금지(P1규칙)");
|
||||
_cashRaiseCands_.slice(0, 8).forEach((c, i) => {
|
||||
const pStr = (c.profit_pct !== "" && c.profit_pct !== null)
|
||||
? ` (${Number(c.profit_pct) >= 0 ? "+" : ""}${Number(c.profit_pct).toFixed(1)}%)`
|
||||
: "";
|
||||
const etfTag = c.is_etf ? "[ETF]" : "";
|
||||
const clTag = c.is_core_leader ? "[주도주⛔매도금지]" : "";
|
||||
L.push(` ${i+1}. ${c.tier_label} ${c.name}${etfTag}${clTag} W:${c.weight_pct}%${pStr} RW:${c.rw_partial} Score:${c.sell_priority_score}`);
|
||||
if (c.trim_style || c.rebound_holdback_score)
|
||||
L.push(` └ trim=${c.trim_style || "N/A"} rebound_holdback=${c.rebound_holdback_score ?? 0}${c.rebound_holdback_reason ? ` | ${c.rebound_holdback_reason}` : ""}`);
|
||||
if (c.action_params) L.push(` └ ${c.action_params}`);
|
||||
if (c.hold_reason) L.push(` └ ⚠ ${c.hold_reason}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 주의 종목 섹션
|
||||
if (stage2Pass.length || timeStopNear.length || overweight.length || tp1Near.length) {
|
||||
L.push("[주의]");
|
||||
stage2Pass.forEach(h => L.push(` ${h.Name} Stage2_Gate=PASS → 2단계 진입 검토 (진입가 ${h.Limit_Price_Est ?? "N/A"})`));
|
||||
timeStopNear.forEach(h => L.push(` ${h.Name} Time_Stop ${h.Days_To_Time_Stop}일 남음 (${h.Time_Stop_Date})`));
|
||||
overweight.forEach(h => L.push(` ${h.Name} OVERWEIGHT ${h.Weight_Pct}% (상한 7%)`));
|
||||
tp1Near.forEach(h => L.push(` ${h.Name} +${h.Profit_Pct}% → TP1(${h.TP1_Price}원) 근접`));
|
||||
}
|
||||
if (events.upcoming_7d?.length) {
|
||||
L.push("[7일 이벤트]");
|
||||
events.upcoming_7d.forEach(ev => L.push(` ${ev.Date}(D+${ev.DaysLeft}) ${ev.Event} [${ev.Impact}]`));
|
||||
}
|
||||
|
||||
// brief_ — holdings row → JSON 요약 (API 소비자용)
|
||||
const brief_ = (h) => ({
|
||||
ticker: h.Ticker, name: h.Name,
|
||||
final_action: h.Final_Action, // canonical output field
|
||||
action_reason: h.Action_Reason, // 왜 이 액션인가
|
||||
action_params: h.Action_Params, // 실행 파라미터 압축 (C-3)
|
||||
final_rank: h.Final_Rank,
|
||||
allowed_action: h.Allowed_Action,
|
||||
ss001_grade: h.SS001_Grade, ss001_norm_score: h.SS001_Norm_Score,
|
||||
rw_partial: h.RW_Partial,
|
||||
weight_pct: h.Weight_Pct, profit_pct: h.Profit_Pct,
|
||||
stage2_gate: h.Stage2_Gate, band_status: h.Band_Status,
|
||||
limit_price_est: h.Limit_Price_Est,
|
||||
stop_price_est: h.Stop_Price_Est, stop_price_source: h.Stop_Price_Source,
|
||||
pos_size_qty: h.Pos_Size_Qty, pos_size_constraint: h.Pos_Size_Constraint,
|
||||
tp1_price: h.TP1_Price, tp1_qty: h.TP1_Qty,
|
||||
tp2_price: h.TP2_Price, tp2_qty: h.TP2_Qty,
|
||||
entry_mode: h.Entry_Mode, entry_mode_gate: h.Entry_Mode_Gate,
|
||||
entry_mode_reason: h.Entry_Mode_Reason,
|
||||
timing_score_entry: h.Timing_Score_Entry,
|
||||
timing_score_exit: h.Timing_Score_Exit,
|
||||
timing_action: h.Timing_Action,
|
||||
timing_block_reason: h.Timing_Block_Reason,
|
||||
sell_action: h.Sell_Action,
|
||||
sell_ratio_pct: h.Sell_Ratio_Pct,
|
||||
sell_limit_price: h.Sell_Limit_Price,
|
||||
sell_reason: h.Sell_Reason,
|
||||
sell_validation: h.Sell_Validation,
|
||||
cash_preserve_style: h.Cash_Preserve_Style || "",
|
||||
cash_preserve_ratio: h.Cash_Preserve_Ratio || "",
|
||||
cash_preserve_reason: h.Cash_Preserve_Reason || "",
|
||||
rsi14: h.RSI14, disparity: h.Disparity, ma20_slope: h.MA20_Slope,
|
||||
exit_signal_detail: h.Exit_Signal_Detail,
|
||||
});
|
||||
|
||||
return {
|
||||
date: today,
|
||||
brief_text: L.join("\n"),
|
||||
market: {
|
||||
regime: macro.market_regime, mrs_score: macro.mrs_score,
|
||||
vix: macro.vix, kospi: macro.kospi, usd_krw: macro.usd_krw,
|
||||
sp500_ret5d: macro.sp500_ret5d,
|
||||
},
|
||||
portfolio_health: {
|
||||
heat_pct: macro.total_heat_pct, heat_ok: heatOk,
|
||||
heat_tag: heatTag,
|
||||
heat_block: heatBlockB, heat_caution: heatCautionB,
|
||||
fc_budget_pct: macro.fc_budget_pct, fc_ok: fcOk,
|
||||
net_return_feedback: nrf,
|
||||
bucket_status: macro.bucket_status,
|
||||
regime_buy_blocked: isRiskOffB,
|
||||
bayesian_label: macro.bayesian_label,
|
||||
bayesian_multiplier: macro.bayesian_multiplier,
|
||||
},
|
||||
orbit: {
|
||||
gap_pct: macro.orbit_gap_pct, state: macro.orbit_state,
|
||||
slot_adjustment: orbitAdj, cash_adjustment: macro.orbit_cash_adj,
|
||||
},
|
||||
// Final_Action canonical 분류 (A-1/B-1)
|
||||
actions: {
|
||||
sell_ready: sellList.map(brief_),
|
||||
exit_signals: exitList.map(brief_),
|
||||
buy_signals: buyList.map(brief_),
|
||||
watch_signals: watchList.map(brief_),
|
||||
hold_signals: holdList.map(brief_),
|
||||
},
|
||||
alerts: {
|
||||
stage2_ready: stage2Pass.map(h=>({ticker:h.Ticker,name:h.Name,profit_pct:h.Profit_Pct,limit_price_est:h.Limit_Price_Est})),
|
||||
time_stop_near: timeStopNear.map(h=>({ticker:h.Ticker,name:h.Name,days_left:h.Days_To_Time_Stop,stop_date:h.Time_Stop_Date})),
|
||||
overweight: overweight.map(h=>({ticker:h.Ticker,name:h.Name,weight_pct:h.Weight_Pct})),
|
||||
tp1_near: tp1Near.map(h=>({ticker:h.Ticker,name:h.Name,profit_pct:h.Profit_Pct,tp1_price:h.TP1_Price,tp2_price:h.TP2_Price})),
|
||||
},
|
||||
upcoming_events: events.upcoming_7d,
|
||||
account_snapshot_freshness: acctFresh,
|
||||
data_quality: {
|
||||
price_stale: priceStaleList_.map(h=>({ticker:h.Ticker,name:h.Name,price_date:h.Price_Date})),
|
||||
quote_only: quoteOnlyList_.map(h=>({ticker:h.Ticker,name:h.Name})),
|
||||
flow_stale: flowStaleList_.map(h=>({ticker:h.Ticker,name:h.Name,missing_fields:h.Missing_Fields})),
|
||||
},
|
||||
// sell_priority_engine 출력 (spec: portfolio_exposure.yaml:sell_priority_engine)
|
||||
// 활성화: REGIME_TRIM_50 또는 현금 부족. ETF→손실위성→코어주도주 순서로 정렬.
|
||||
cash_raise: _cashBelowTgt_ ? {
|
||||
active: true,
|
||||
reason: isRiskOffB ? `REGIME_TRIM_50(${regimeStr})` : "cash_below_target",
|
||||
prohibition: "매도수량은 HTS 캡처 제공 후 결정. 수량 미제공 시 수량 기재 금지(spec:P1규칙).",
|
||||
sell_priority_table: _cashRaiseCands_,
|
||||
sector_exposure_summary: sellPriorityView_.sector_exposure ?? sellPriorityView_.sector_exposure_summary ?? {},
|
||||
} : { active: false },
|
||||
};
|
||||
}
|
||||
|
||||
// ── E3: 거래 진입 템플릿 생성 ────────────────────────────────────────────────
|
||||
// BUY_CANDIDATE/WATCH_CANDIDATE 종목에 대해 performance 탭 입력 행 + 진입 체크리스트 반환.
|
||||
// doGet(?view=trade_template&ticker=064350)
|
||||
function getTradeTemplate(ticker) {
|
||||
if (!ticker) return { error: "ticker 파라미터 필요 (?view=trade_template&ticker=XXXXXX)" };
|
||||
const allData = sheetToJson("data_feed");
|
||||
const row = allData.find(r => String(r.Ticker) === String(ticker) || r.Name === ticker);
|
||||
if (!row) return { error: `ticker ${ticker} not found in data_feed` };
|
||||
|
||||
const macro = getMacroJson();
|
||||
const today = Utilities.formatDate(new Date(), "Asia/Seoul", "yyyy-MM-dd");
|
||||
const sector = TICKER_SECTOR_MAP[ticker] ?? "N/A";
|
||||
|
||||
// 진입 체크리스트 — 각 항목 true/false
|
||||
const checklist = {
|
||||
data_quality: row.Price_Status === "PRICE_OK",
|
||||
no_dart_risk: !row.DART_Risk || row.DART_Risk === "" || row.DART_Risk === "N",
|
||||
liquidity_ok: row.Liquidity_Status === "OK",
|
||||
timing_ready: ["BUY_STAGE1_READY","BUY_PULLBACK_WAIT","BUY_BREAKOUT_PILOT_ONLY"].includes(row.Timing_Action),
|
||||
leader_gate: ["PASS","EXPLORE_CANDIDATE","WATCH_ONLY"].includes(row.Leader_Gate),
|
||||
ac_gate: row.AC_Gate === "CLEAR",
|
||||
flow_credit_ok: parseFloat(row.Flow_Credit) >= 0.4,
|
||||
regime_ok: ["RISK_ON","SECULAR_LEADER_RISK_ON","LEADER_CONCENTRATION"].includes(macro.market_regime),
|
||||
heat_ok: Number.isFinite(parseFloat(macro.total_heat_pct)) && parseFloat(macro.total_heat_pct) < 10,
|
||||
fc_budget_ok: Number.isFinite(parseFloat(macro.fc_budget_pct)) && parseFloat(macro.fc_budget_pct) < 100,
|
||||
nr_feedback_ok: macro.net_return_feedback !== "REDUCED",
|
||||
ee_positive: parseFloat(row.EE_Est) > 0,
|
||||
ss001_grade_ok: ["A","B"].includes(row.SS001_Grade),
|
||||
};
|
||||
const passCount = Object.values(checklist).filter(Boolean).length;
|
||||
const totalCheck = Object.keys(checklist).length;
|
||||
const gateStatus = passCount === totalCheck ? "ALL_PASS"
|
||||
: passCount >= totalCheck - 2 ? "MINOR_ISSUES"
|
||||
: "BLOCK";
|
||||
|
||||
return {
|
||||
ticker,
|
||||
name: row.Name,
|
||||
sector,
|
||||
generated_at: today,
|
||||
gate_status: gateStatus,
|
||||
gate_score: `${passCount}/${totalCheck}`,
|
||||
checklist,
|
||||
// performance 탭에 바로 붙여넣을 수 있는 행 템플릿
|
||||
performance_tab_template: {
|
||||
trade_id: `${today.replace(/-/g,"")}${ticker}`,
|
||||
ticker,
|
||||
sector,
|
||||
entry_date: today,
|
||||
entry_price: row.Limit_Price_Est ?? "",
|
||||
entry_stage: "stage_1",
|
||||
quantity: row.Pos_Size_Qty ?? "",
|
||||
stop_price_at_entry: row.Stop_Price_Est ?? "",
|
||||
target_price_at_entry: row.Target_Price ?? "",
|
||||
exit_date: "",
|
||||
exit_price: "",
|
||||
exit_reason: "",
|
||||
pnl_pct: "",
|
||||
holding_days: "",
|
||||
entry_c1_score: row.C1_Price ?? "",
|
||||
entry_c2_score: row.C2_RelStr ?? "",
|
||||
entry_c3_score: row.C3_VolSurge ?? "",
|
||||
entry_c4_score: row.C4_Flow ?? "",
|
||||
entry_c5_score: row.C5_Sector ?? "",
|
||||
entry_mode: row.Entry_Mode ?? "",
|
||||
entry_gate: row.Entry_Mode_Gate ?? "",
|
||||
timing_action: row.Timing_Action ?? "",
|
||||
timing_score_entry: row.Timing_Score_Entry ?? "",
|
||||
timing_score_exit: row.Timing_Score_Exit ?? "",
|
||||
anti_climax_gate: row.AC_Gate ?? "",
|
||||
flow_credit: row.Flow_Credit ?? "",
|
||||
entry_mrs_score: macro.mrs_score ?? "",
|
||||
fc_bucket: "",
|
||||
},
|
||||
current_state: {
|
||||
close: row.Close,
|
||||
allowed_action: row.Allowed_Action,
|
||||
timing_action: row.Timing_Action,
|
||||
timing_score_entry: row.Timing_Score_Entry,
|
||||
timing_score_exit: row.Timing_Score_Exit,
|
||||
timing_block_reason: row.Timing_Block_Reason,
|
||||
sell_action: row.Sell_Action,
|
||||
sell_ratio_pct: row.Sell_Ratio_Pct,
|
||||
sell_qty: row.Sell_Qty,
|
||||
sell_limit_price: row.Sell_Limit_Price,
|
||||
sell_price_source: row.Sell_Price_Source,
|
||||
sell_reason: row.Sell_Reason,
|
||||
sell_validation: row.Sell_Validation,
|
||||
ss001_grade: row.SS001_Grade,
|
||||
ss001_total: row.SS001_Total,
|
||||
flow_credit: row.Flow_Credit,
|
||||
rw_partial: row.RW_Partial,
|
||||
limit_price_est: row.Limit_Price_Est,
|
||||
stop_price_est: row.Stop_Price_Est,
|
||||
stop_price_source: row.Stop_Price_Source,
|
||||
ee_est: row.EE_Est,
|
||||
pos_size_qty: row.Pos_Size_Qty,
|
||||
upside_pct: row.Upside_Pct,
|
||||
atr20: row.ATR20,
|
||||
tp1_price: row.TP1_Price,
|
||||
tp1_qty: row.TP1_Qty,
|
||||
tp2_price: row.TP2_Price,
|
||||
tp2_qty: row.TP2_Qty,
|
||||
dart_risk: row.DART_Risk,
|
||||
days_to_earnings: row.Days_To_Earnings,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSummaryJson() {
|
||||
// ChatGPT 포트폴리오 분석에 최적화된 통합 뷰
|
||||
const sectors = getSectorFlowJson();
|
||||
const port = getPortfolioJson();
|
||||
const macro = getMacroJson();
|
||||
const events = getEventRiskJson();
|
||||
|
||||
// 포트폴리오 전체 수급 요약
|
||||
const holdings = port.holdings;
|
||||
const totalFrg5 = holdings.reduce((s,h) => s + (parseFloat(h.Frg_5D) || 0), 0);
|
||||
const totalInst5 = holdings.reduce((s,h) => s + (parseFloat(h.Inst_5D) || 0), 0);
|
||||
const flowOkCount = holdings.filter(h => h.Flow_OK === "Y").length;
|
||||
|
||||
// SS001 등급 분포 및 Allowed_Action 집계
|
||||
const ss001Dist = { A: 0, B: 0, C: 0, D: 0 };
|
||||
const actionDist = {};
|
||||
holdings.forEach(h => {
|
||||
const g = h["SS001_Grade"];
|
||||
if (g in ss001Dist) ss001Dist[g]++;
|
||||
const a = h["Allowed_Action"] || "UNKNOWN";
|
||||
actionDist[a] = (actionDist[a] ?? 0) + 1;
|
||||
});
|
||||
|
||||
return {
|
||||
portfolio_flow_summary: {
|
||||
total_holdings: holdings.length,
|
||||
data_ok_count: flowOkCount,
|
||||
portfolio_frg_5d_total: totalFrg5,
|
||||
portfolio_inst_5d_total: totalInst5,
|
||||
portfolio_indiv_5d_total: -(totalFrg5 + totalInst5),
|
||||
},
|
||||
ss001_grade_distribution: ss001Dist,
|
||||
action_distribution: actionDist,
|
||||
sector_summary: {
|
||||
total_sectors: sectors.count,
|
||||
top_inflow_sectors: sectors.top_inflow,
|
||||
outflow_warning_sectors: sectors.outflow_warning,
|
||||
strong_smart_money_sectors: sectors.strong_smart_money,
|
||||
},
|
||||
macro_snapshot: {
|
||||
vix: macro.vix,
|
||||
usd_krw: macro.usd_krw,
|
||||
kospi: macro.kospi,
|
||||
sp500_5d_ret: macro.sp500_ret5d,
|
||||
market_regime: macro.market_regime,
|
||||
mrs_score: macro.mrs_score,
|
||||
bayesian_multiplier: macro.bayesian_multiplier,
|
||||
total_heat_pct: macro.total_heat_pct,
|
||||
fc_budget_pct: macro.fc_budget_pct,
|
||||
net_return_feedback: macro.net_return_feedback,
|
||||
orbit_gap_pct: macro.orbit_gap_pct,
|
||||
orbit_state: macro.orbit_state,
|
||||
orbit_slot_adj: macro.orbit_slot_adj,
|
||||
bucket_status: macro.bucket_status,
|
||||
bucket_detail: macro.bucket_detail,
|
||||
},
|
||||
event_alerts: events.upcoming_7d,
|
||||
holdings_detail: holdings,
|
||||
sector_detail: sectors.sectors,
|
||||
macro_detail: macro.indicators,
|
||||
macro_computed: macro.computed_summary,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# ADR-0001 Adopt active artifact manifest
|
||||
|
||||
## Context
|
||||
Temp contains multiple artifact versions and the runtime must read only one active path per artifact key.
|
||||
|
||||
## Decision
|
||||
Use `runtime/active_artifact_manifest.yaml` as the only runtime selection source.
|
||||
|
||||
See `spec/09_decision_flow.yaml` and `tools/validate_active_manifest.py` for the runtime gate and selection checks.
|
||||
|
||||
## Alternatives
|
||||
- Scan Temp directly
|
||||
- Pick latest timestamp
|
||||
|
||||
## Consequences
|
||||
- Deterministic runtime file selection
|
||||
- Lower stale-read risk
|
||||
|
||||
## Rollback
|
||||
Restore prior manifest only if validation fails.
|
||||
|
||||
## Affected files
|
||||
- runtime/active_artifact_manifest.yaml
|
||||
- spec/09_decision_flow.yaml
|
||||
- tools/validate_active_manifest.py
|
||||
@@ -0,0 +1,22 @@
|
||||
# ADR-0002 Authority matrix
|
||||
|
||||
## Context
|
||||
Output fields need a single declared owner to prevent duplicate writers.
|
||||
|
||||
## Decision
|
||||
Maintain a governance authority matrix and output-field owner ledger.
|
||||
|
||||
## Alternatives
|
||||
- Embed ownership in ad hoc notes
|
||||
|
||||
## Consequences
|
||||
- Ownership collision checks become explicit
|
||||
- Review is easier for numeric outputs
|
||||
|
||||
## Rollback
|
||||
Keep the prior ledger alongside the new one.
|
||||
|
||||
## Affected files
|
||||
- governance/authority_matrix.yaml
|
||||
- spec/03_formulas/output_field_owner_ledger.yaml
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# ADR-0003 Baseline metrics
|
||||
|
||||
## Context
|
||||
Refactor progress needs a fixed baseline.
|
||||
|
||||
## Decision
|
||||
Record repository baseline metrics as build output.
|
||||
|
||||
See `tools/validate_calibration_registry_v1.py` and `spec/08_scoring_rules.yaml` for baseline-linked validation context.
|
||||
|
||||
## Alternatives
|
||||
- Report metrics manually in review notes
|
||||
|
||||
## Consequences
|
||||
- Delta tracking becomes deterministic
|
||||
|
||||
## Rollback
|
||||
Regenerate from a prior commit if needed.
|
||||
|
||||
## Affected files
|
||||
- Temp/refactor_baseline_metrics_v1.json
|
||||
- tools/validate_calibration_registry_v1.py
|
||||
- spec/08_scoring_rules.yaml
|
||||
@@ -0,0 +1,21 @@
|
||||
# ADR-0004 Rule lifecycle
|
||||
|
||||
## Context
|
||||
Rule proliferation needs a lifecycle policy.
|
||||
|
||||
## Decision
|
||||
Use proposed -> shadow -> active -> deprecated -> removed.
|
||||
|
||||
## Alternatives
|
||||
- Free-form status labels
|
||||
|
||||
## Consequences
|
||||
- Deprecation is explicit
|
||||
- Active router reference checks become possible
|
||||
|
||||
## Rollback
|
||||
Downgrade the rule to shadow if needed.
|
||||
|
||||
## Affected files
|
||||
- governance/rule_lifecycle.yaml
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# ADR-0005 Formula domain split
|
||||
|
||||
## Context
|
||||
The monolithic formula registry needs domain separation.
|
||||
|
||||
## Decision
|
||||
Split formulas by domain and build a normalized registry.
|
||||
|
||||
## Alternatives
|
||||
- Keep a single registry file
|
||||
|
||||
## Consequences
|
||||
- Review scope is reduced
|
||||
- Formula ownership is easier to track
|
||||
|
||||
## Rollback
|
||||
Rebuild normalized registry from the monolith.
|
||||
|
||||
## Affected files
|
||||
- spec/formulas/*
|
||||
- spec/03_formulas/formula_registry.normalized.yaml
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# ADR-0006 Final decision packet
|
||||
|
||||
## Context
|
||||
The renderer needs a single canonical packet.
|
||||
|
||||
## Decision
|
||||
Use a versioned final decision packet as the only render input.
|
||||
|
||||
See `spec/07_output_schema.yaml` and `tools/validate_report_packet_sync_v1.py` for the canonical packet/render contract.
|
||||
|
||||
## Alternatives
|
||||
- Read many Temp files directly
|
||||
|
||||
## Consequences
|
||||
- Provenance checks become centralized
|
||||
|
||||
## Rollback
|
||||
Keep the previous packet version available.
|
||||
|
||||
## Affected files
|
||||
- Temp/final_decision_packet_v3.json
|
||||
- Temp/final_decision_packet_active.json
|
||||
- spec/07_output_schema.yaml
|
||||
- tools/validate_report_packet_sync_v1.py
|
||||
@@ -0,0 +1,20 @@
|
||||
# ADR-0007 Shadow ledger
|
||||
|
||||
## Context
|
||||
Blocked items still need visible computed values.
|
||||
|
||||
## Decision
|
||||
Separate executable order table from shadow ledger.
|
||||
|
||||
## Alternatives
|
||||
- Hide blocked numbers
|
||||
|
||||
## Consequences
|
||||
- User review is preserved
|
||||
|
||||
## Rollback
|
||||
Show the prior packet alongside the new ledger if needed.
|
||||
|
||||
## Affected files
|
||||
- spec/outputs/shadow_ledger_contract.yaml
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# ADR-0008 Renderer contract
|
||||
|
||||
## Context
|
||||
Low-capability LLM rendering needs a closed contract.
|
||||
|
||||
## Decision
|
||||
Fix the input packet, section order, and numeric copy rules.
|
||||
|
||||
## Alternatives
|
||||
- Free-form narrative generation
|
||||
|
||||
## Consequences
|
||||
- Lower hallucination risk
|
||||
|
||||
## Rollback
|
||||
Use the prior prompt only if schema validation fails.
|
||||
|
||||
## Affected files
|
||||
- prompts/low_capability_report_renderer.md
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# ADR-0009 Number provenance audit
|
||||
|
||||
## Context
|
||||
Every investment number must be traceable.
|
||||
|
||||
## Decision
|
||||
Require source_ref and formula_id for investment numbers.
|
||||
|
||||
## Alternatives
|
||||
- Allow implicit report numbers
|
||||
|
||||
## Consequences
|
||||
- Ungrounded numbers are detectable
|
||||
|
||||
## Rollback
|
||||
Rebuild the report from a provenance-backed packet.
|
||||
|
||||
## Affected files
|
||||
- tools/validate_number_provenance_v2.py
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# ADR-0010 Change request template
|
||||
|
||||
## Context
|
||||
Rule and formula changes need structured evidence.
|
||||
|
||||
## Decision
|
||||
Use a mandatory change-request template for active changes.
|
||||
|
||||
## Alternatives
|
||||
- Free-form change notes
|
||||
|
||||
## Consequences
|
||||
- Rollback and testing become explicit
|
||||
|
||||
## Rollback
|
||||
Reject changes that lack the required fields.
|
||||
|
||||
## Affected files
|
||||
- governance/change_request_template.yaml
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# ADR-0011: QEDD 방법론 채택
|
||||
|
||||
## 상태
|
||||
준비됨 (Proposed)
|
||||
|
||||
## 배경
|
||||
퀀트 엔진의 복잡도가 증가함에 따라, 저성능 LLM에서도 동일한 투자 결론을 재현하고 운영 안정성을 확보하기 위한 결정론적 개발 방법론이 필요함.
|
||||
|
||||
## 결정
|
||||
QEDD(Quant Evidence-Driven Deterministic Development) 방법론을 채택함.
|
||||
1. 모든 판단 로직은 YAML 계약(spec)에 근거함.
|
||||
2. 모든 수치 계산은 Python Canonical 엔진에서만 수행함.
|
||||
3. GAS는 데이터 수집 및 입출력 어댑터 역할로 축소함.
|
||||
4. LLM은 이미 계산된 패킷을 복사하여 렌더링하는 역할만 수행함 (Math 금지).
|
||||
|
||||
## 결과
|
||||
- 운영 보고서의 숫자 신뢰도 100% 확보.
|
||||
- 저성능 모델에서도 판단 번복 없는 안정적인 운영 가능.
|
||||
- 아키텍처 경계 위반 자동 차단.
|
||||
@@ -0,0 +1,44 @@
|
||||
schema_version: adr_index.v1
|
||||
adr_count: 10
|
||||
entries:
|
||||
- adr_id: ADR-0001
|
||||
title: Adopt active artifact manifest
|
||||
status: accepted
|
||||
path: governance/adr/0001-adopt-active-artifact-manifest.md
|
||||
- adr_id: ADR-0002
|
||||
title: Adopt authority matrix
|
||||
status: accepted
|
||||
path: governance/adr/0002-authority-matrix.md
|
||||
- adr_id: ADR-0003
|
||||
title: Freeze baseline metrics
|
||||
status: accepted
|
||||
path: governance/adr/0003-baseline-metrics.md
|
||||
- adr_id: ADR-0004
|
||||
title: Introduce rule lifecycle policy
|
||||
status: accepted
|
||||
path: governance/adr/0004-rule-lifecycle.md
|
||||
- adr_id: ADR-0005
|
||||
title: Split formula registry by domain
|
||||
status: accepted
|
||||
path: governance/adr/0005-formula-domain-split.md
|
||||
- adr_id: ADR-0006
|
||||
title: Make final decision packet canonical
|
||||
status: accepted
|
||||
path: governance/adr/0006-final-decision-packet.md
|
||||
- adr_id: ADR-0007
|
||||
title: Mandatory shadow ledger
|
||||
status: accepted
|
||||
path: governance/adr/0007-shadow-ledger.md
|
||||
- adr_id: ADR-0008
|
||||
title: Low capability renderer contract
|
||||
status: accepted
|
||||
path: governance/adr/0008-renderer-contract.md
|
||||
- adr_id: ADR-0009
|
||||
title: Number provenance audit
|
||||
status: accepted
|
||||
path: governance/adr/0009-number-provenance.md
|
||||
- adr_id: ADR-0010
|
||||
title: Change request template
|
||||
status: accepted
|
||||
path: governance/adr/0010-change-request-template.md
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
schema_version: agents_index.v1
|
||||
source: AGENTS.md
|
||||
purpose: Compact index for operator rules migrated out of AGENTS.md.
|
||||
rule_files:
|
||||
- governance/rules/00_core_locks.yaml
|
||||
- governance/rules/01_harness_contract.yaml
|
||||
- governance/rules/02_portfolio_policy.yaml
|
||||
- governance/rules/03_order_grammar.yaml
|
||||
- governance/rules/04_reporting_contract.yaml
|
||||
- governance/rules/05_migration_hashes.yaml
|
||||
hash_manifest: governance/agents_rule_hashes.yaml
|
||||
hash_algorithm: sha256
|
||||
@@ -0,0 +1,18 @@
|
||||
schema_version: agents_rule_hashes.v1
|
||||
hash_algorithm: sha256
|
||||
generated_from: governance/agents_index.yaml
|
||||
files:
|
||||
- path: governance/rules/00_core_locks.yaml
|
||||
sha256: b3c3d7ce05beb9e8b0945d98a0a1a55276254acef246c13f8c3a110f14f57ff4
|
||||
- path: governance/rules/01_harness_contract.yaml
|
||||
sha256: a093ddafa4a1b624ee44e4a98a63ce196ad452572fb27418c7e82b9b5edafc5a
|
||||
- path: governance/rules/02_portfolio_policy.yaml
|
||||
sha256: 47f6f33602482213523e6fdfa191309a34b805fc7acbe4aa84f475ece899a8ad
|
||||
- path: governance/rules/03_order_grammar.yaml
|
||||
sha256: cbcde916be0929cb1ba7fdbe9922c4445e375ea5d39654d96b86e1e80313cca9
|
||||
- path: governance/rules/04_reporting_contract.yaml
|
||||
sha256: 6ec102fcd3f8c50325ca793b8709200ec688526673405f594e5a03c137300f7b
|
||||
- path: governance/rules/05_migration_hashes.yaml
|
||||
sha256: fed17361105a22161e974b9503a5908c8d332f66b19503a6d6a4d12ceabaef75
|
||||
- path: AGENTS.md
|
||||
sha256: 8d7748597bb248c46db6089cd09da8d4af3f7eadc55243f8afaf4aebaea82fcf
|
||||
@@ -0,0 +1,10 @@
|
||||
formula_id: FORMULA_AUTHORITY_MATRIX_V1
|
||||
generated_at: '2026-06-06T00:00:00+09:00'
|
||||
owned_output_field_pct: 100.0
|
||||
authority_collision_count: 0
|
||||
manual_override_field_count: 0
|
||||
source:
|
||||
formula_owner_coverage: Temp/formula_owner_coverage_v1.json
|
||||
output_field_collision: Temp/output_field_owner_collision_v1.json
|
||||
output_field_ledger: spec/03_formulas/output_field_owner_ledger.yaml
|
||||
field_dictionary: spec/12_field_dictionary.yaml
|
||||
@@ -0,0 +1,18 @@
|
||||
change_request_template_version: v1.0
|
||||
proposal_metadata:
|
||||
proposal_id: "CR-YYYYMMDD-NAME"
|
||||
author: "quant_architect"
|
||||
created_at: "YYYY-MM-DD"
|
||||
rationale:
|
||||
why: "State the reason for this change request"
|
||||
expected_edge: "Describe the expected analytical edge or performance lift"
|
||||
risk: "Outline potential failure modes and risk exposure changes"
|
||||
dependencies:
|
||||
data_dependency: "List of workbook columns or API data sources required"
|
||||
verification:
|
||||
tests:
|
||||
- "List unit/regression tests or golden cases that validate this change"
|
||||
rollback_plan: "Actionable rollback steps if the change fails verification"
|
||||
outcome_metrics:
|
||||
expected_t5_pass_rate_pct: 60.0
|
||||
expected_expectancy: 0.10
|
||||
@@ -0,0 +1,18 @@
|
||||
change_request_template_version: v1.0
|
||||
proposal_metadata:
|
||||
proposal_id: "CR-0001"
|
||||
author: "quant_architect"
|
||||
created_at: "2026-06-03"
|
||||
rationale:
|
||||
why: "Example change request placeholder"
|
||||
expected_edge: "improved provenance visibility"
|
||||
risk: "none"
|
||||
dependencies:
|
||||
data_dependency: "existing methodology todo"
|
||||
verification:
|
||||
tests:
|
||||
- "validate_final_decision_packet"
|
||||
rollback_plan: "revert packet builder"
|
||||
outcome_metrics:
|
||||
expected_t5_pass_rate_pct: 60.0
|
||||
expected_expectancy: 0.10
|
||||
@@ -0,0 +1,18 @@
|
||||
change_request_template_version: v1.0
|
||||
proposal_metadata:
|
||||
proposal_id: "CR-20260607-SAMPLE"
|
||||
author: "quant_architect"
|
||||
created_at: "2026-06-07"
|
||||
rationale:
|
||||
why: "Integrate property and metamorphic invariants"
|
||||
expected_edge: "Stable execution decisions"
|
||||
risk: "Increase computation time slightly"
|
||||
dependencies:
|
||||
data_dependency: "spec/property_invariants.yaml"
|
||||
verification:
|
||||
tests:
|
||||
- "validate_property_invariants"
|
||||
rollback_plan: "Remove validate_property_invariants from DAG"
|
||||
outcome_metrics:
|
||||
expected_t5_pass_rate_pct: 65.0
|
||||
expected_expectancy: 0.12
|
||||
@@ -0,0 +1,205 @@
|
||||
schema_version: gas_logic_migration_ledger.v1
|
||||
source: governance/gas_logic_migration_ledger_v1.yaml
|
||||
authored: 2026-06-10
|
||||
total_findings: 15
|
||||
classification_summary:
|
||||
decision_logic: 4
|
||||
score_logic: 5
|
||||
price_qty_logic: 4
|
||||
pure_mapping: 1
|
||||
display_text: 1
|
||||
unclassified_findings: 0
|
||||
|
||||
# Canonical classification of GAS thin-adapter findings identified by
|
||||
# validate_gas_thin_adapter_v1.py. Each finding is classified by what type
|
||||
# of logic it contains and paired with a migration_action.
|
||||
findings:
|
||||
- id: F01
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 186
|
||||
text: "SP_TAKE_PROFIT: 10, // Profit_Pct >= 10% (익절 후보)"
|
||||
classification: score_logic
|
||||
migration_action: REGISTER_SP_TAKE_PROFIT
|
||||
target_file: formulas/score_thresholds_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F02
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 656
|
||||
text: "priceBasis = Number.isFinite(tp2Price) ? \"TAKE_PROFIT_TIER2_PRICE\" : \"PRIOR_CLOSE_X_0.998\";"
|
||||
classification: price_qty_logic
|
||||
migration_action: MIGRATE_PRICEBASIS_TO_PYTHON
|
||||
target_file: formulas/price_basis_v1.py
|
||||
status: TODO
|
||||
blocking_on: F03 F04 (same function, migrate together)
|
||||
|
||||
- id: F03
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 665
|
||||
text: "priceBasis = Number.isFinite(tp2Price) ? \"TAKE_PROFIT_TIER2_PRICE\" : \"PRIOR_CLOSE_X_0.998\";"
|
||||
classification: price_qty_logic
|
||||
migration_action: MIGRATE_PRICEBASIS_TO_PYTHON
|
||||
target_file: formulas/price_basis_v1.py
|
||||
status: TODO
|
||||
blocking_on: F02 F04
|
||||
|
||||
- id: F04
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 674
|
||||
text: "priceBasis = Number.isFinite(tp1Price) ? \"TAKE_PROFIT_TIER1_PRICE\" : \"PRIOR_CLOSE_X_0.998\";"
|
||||
classification: price_qty_logic
|
||||
migration_action: MIGRATE_PRICEBASIS_TO_PYTHON
|
||||
target_file: formulas/price_basis_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F05
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 678
|
||||
text: "action = \"TAKE_PROFIT_TIER1\";"
|
||||
classification: decision_logic
|
||||
migration_action: MIGRATE_DECISIONS_ROUTING
|
||||
target_file: formulas/execution_decision_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F06
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 683
|
||||
text: "priceBasis = Number.isFinite(tp1Price) ? \"TAKE_PROFIT_TIER1_PRICE\" : \"PRIOR_CLOSE_X_0.998\";"
|
||||
classification: price_qty_logic
|
||||
migration_action: MIGRATE_PRICEBASIS_TO_PYTHON
|
||||
target_file: formulas/price_basis_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F07
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 1577
|
||||
text: "score += THRESHOLDS[\"SP_TAKE_PROFIT\"];"
|
||||
classification: score_logic
|
||||
migration_action: MIGRATE_SCORE_CALCULATION
|
||||
target_file: formulas/score_thresholds_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F08
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 1578
|
||||
text: "breakdown.push(`take_profit:+${THRESHOLDS[\"SP_TAKE_PROFIT\"]}`)"
|
||||
classification: display_text
|
||||
migration_action: DISPLAY_TEXT_PASSTHROUGH
|
||||
notes: display_text stays in GAS adapter as rendering concern
|
||||
status: KEEP_IN_GAS
|
||||
|
||||
- id: F09
|
||||
file: src/gas_adapter_parts/gdf_01_price_metrics.gs
|
||||
line: 2164
|
||||
text: "TAKE_PROFIT_BASE: 10,"
|
||||
classification: score_logic
|
||||
migration_action: REGISTER_TAKE_PROFIT_BASE
|
||||
target_file: formulas/score_thresholds_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F10
|
||||
file: src/gas_adapter_parts/gdf_03_portfolio_gates.gs
|
||||
line: 1335
|
||||
text: "return { [\"decisions\"]: routes, traces: traces, lock: true };"
|
||||
classification: decision_logic
|
||||
migration_action: MIGRATE_DECISIONS_ROUTING
|
||||
target_file: formulas/routing_decision_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F11
|
||||
file: src/gas_adapter_parts/gdf_03_portfolio_gates.gs
|
||||
line: 1360
|
||||
text: "if (holding && holding.stopBreach) return 'STOP_LOSS';"
|
||||
classification: decision_logic
|
||||
migration_action: MIGRATE_STOP_BREACH_DECISION
|
||||
target_file: formulas/stop_loss_gate_v1.py
|
||||
status: TODO
|
||||
|
||||
- id: F12
|
||||
file: src/gas_adapter_parts/gdf_03_portfolio_gates.gs
|
||||
line: 2128
|
||||
text: "[\"distribution_risk_score\"]: Math.min(100, Math.max(0, score)),"
|
||||
classification: score_logic
|
||||
migration_action: DELETE_DISTRIBUTION_RISK_GAS
|
||||
target_file: formulas/distribution_risk_v1.py
|
||||
status: TODO
|
||||
notes: Python canonical (build_distribution_risk_v1.py) already exists; GAS version is duplicate
|
||||
|
||||
- id: F13
|
||||
file: src/gas_adapter_parts/gdf_03_portfolio_gates.gs
|
||||
line: 2132
|
||||
text: "formula_id: 'DISTRIBUTION_RISK_SCORE_V1'"
|
||||
classification: pure_mapping
|
||||
migration_action: DELETE_DISTRIBUTION_RISK_GAS
|
||||
status: TODO
|
||||
notes: formula_id tag stays with Python canonical; remove from GAS
|
||||
|
||||
- id: F14
|
||||
file: src/gas_adapter_parts/gdf_03_portfolio_gates.gs
|
||||
line: 2214
|
||||
text: "[\"late_chase_risk_score\"]: Math.min(100, Math.max(0, Math.round(lateChaseRisk))),"
|
||||
classification: score_logic
|
||||
migration_action: DELETE_LATE_CHASE_RISK_GAS
|
||||
target_file: formulas/late_chase_risk_v1.py
|
||||
status: TODO
|
||||
notes: Python canonical (build_alpha_lead_table_v1.py) computes late_chase_risk; GAS version is duplicate
|
||||
|
||||
- id: F15
|
||||
file: src/gas_adapter_parts/gdf_04_execution_quality.gs
|
||||
line: 479
|
||||
text: "if (bqRow.breakout_quality_gate === 'BLOCKED_LATE_CHASE' || alphaRow[\"late_chase_risk_score\"] >= 70)"
|
||||
classification: decision_logic
|
||||
migration_action: MIGRATE_LATE_CHASE_GATE
|
||||
target_file: formulas/late_chase_gate_v1.py
|
||||
status: TODO
|
||||
|
||||
# Migration action summary (9 actions)
|
||||
migration_actions:
|
||||
- action_id: REGISTER_SP_TAKE_PROFIT
|
||||
findings: [F01]
|
||||
description: Register SP_TAKE_PROFIT threshold (value=10) in Python score_thresholds canonical
|
||||
priority: LOW
|
||||
|
||||
- action_id: REGISTER_TAKE_PROFIT_BASE
|
||||
findings: [F09]
|
||||
description: Register TAKE_PROFIT_BASE threshold (value=10) in Python score_thresholds canonical
|
||||
priority: LOW
|
||||
|
||||
- action_id: DELETE_DISTRIBUTION_RISK_GAS
|
||||
findings: [F12, F13]
|
||||
description: Remove distribution_risk_score calculation from gdf_03; Python canonical exists
|
||||
priority: HIGH
|
||||
blocker: verify build_distribution_risk_v1.py output matches GAS output before delete
|
||||
|
||||
- action_id: DELETE_LATE_CHASE_RISK_GAS
|
||||
findings: [F14]
|
||||
description: Remove late_chase_risk_score from gdf_03; Python canonical in alpha_lead_table_v1
|
||||
priority: HIGH
|
||||
blocker: verify parity before delete
|
||||
|
||||
- action_id: MIGRATE_PRICEBASIS_TO_PYTHON
|
||||
findings: [F02, F03, F04, F06]
|
||||
description: priceBasis string selection (TIER1/TIER2 or PRIOR_CLOSE_X_0.998) → Python canonical
|
||||
priority: MEDIUM
|
||||
|
||||
- action_id: MIGRATE_SCORE_CALCULATION
|
||||
findings: [F07]
|
||||
description: score += THRESHOLDS["SP_TAKE_PROFIT"] pattern → Python canonical scorer
|
||||
priority: MEDIUM
|
||||
|
||||
- action_id: MIGRATE_STOP_BREACH_DECISION
|
||||
findings: [F11]
|
||||
description: holding.stopBreach → STOP_LOSS decision → Python canonical stop_loss_gate
|
||||
priority: HIGH
|
||||
notes: critical path — must match validate_stop_loss_policy_v1 spec
|
||||
|
||||
- action_id: MIGRATE_DECISIONS_ROUTING
|
||||
findings: [F05, F10]
|
||||
description: TAKE_PROFIT_TIER1 action assignment and routing lock decision → Python canonical
|
||||
priority: MEDIUM
|
||||
|
||||
- action_id: MIGRATE_LATE_CHASE_GATE
|
||||
findings: [F15]
|
||||
description: BLOCKED_LATE_CHASE gate check (threshold 70) → Python canonical gate formula
|
||||
priority: HIGH
|
||||
blocker: late_chase_risk_score must come from Python before GAS gate can be removed
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version: rule_lifecycle.v1
|
||||
states:
|
||||
- shadow
|
||||
- evidence
|
||||
- active
|
||||
- retire
|
||||
transition_policy:
|
||||
require_change_request: true
|
||||
@@ -0,0 +1,9 @@
|
||||
schema_version: agents_rule.v1
|
||||
rule_id: CORE_LOCKS_V1
|
||||
title: Core locks and no-hallucination rules
|
||||
summary:
|
||||
- Use spec/13_formula_registry.yaml for all prices, stops, targets, quantities.
|
||||
- Do not invent prices, quantities, or formulas.
|
||||
- If harness data is missing, print DATA_MISSING — 하네스 업데이트 필요.
|
||||
- Preserve transparency for blocked items; never hide computed fields.
|
||||
- Use only registered formulas and source-of-truth artifacts.
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version: agents_rule.v1
|
||||
rule_id: HARNESS_CONTRACT_V1
|
||||
title: Harness and validation contract
|
||||
summary:
|
||||
- Harness output must be validated before it is trusted.
|
||||
- LLM must not override harness verdicts.
|
||||
- Source-of-truth precedence follows runtime manifest and schema contracts.
|
||||
- Live T+20 readiness cannot be faked from replay data.
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version: agents_rule.v1
|
||||
rule_id: PORTFOLIO_POLICY_V1
|
||||
title: Portfolio policy contract
|
||||
summary:
|
||||
- Heat, cash floor, stop loss, TP, and sell priority come from specs only.
|
||||
- Do not bypass hard stops with narrative justification.
|
||||
- For multiple sell candidates, show sell priority table first.
|
||||
- Keep blocked positions transparent in shadow ledger outputs.
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version: agents_rule.v1
|
||||
rule_id: ORDER_GRAMMAR_V1
|
||||
title: HTS order grammar
|
||||
summary:
|
||||
- Single numeric price per order row.
|
||||
- No multi-condition conjunctions in order text.
|
||||
- Normalize all prices to KRX tick units.
|
||||
- Remove stale TP prices when TP1 already triggered.
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version: agents_rule.v1
|
||||
rule_id: REPORTING_CONTRACT_V1
|
||||
title: Reporting and provenance contract
|
||||
summary:
|
||||
- All rendered numbers require provenance tags.
|
||||
- Reports must keep source and runtime artifact references explicit.
|
||||
- Narrative must not soften or override numeric gates.
|
||||
- Output schema and final packets are authoritative.
|
||||
@@ -0,0 +1,8 @@
|
||||
schema_version: agents_rule.v1
|
||||
rule_id: RULE_HASH_MIGRATION_V1
|
||||
title: Rule hash migration map
|
||||
summary:
|
||||
- AGENTS.md is the index only.
|
||||
- Rule files carry the detailed instructions.
|
||||
- Hash each rule file and track the set in governance/agents_index.yaml.
|
||||
- Update hashes whenever rule files change.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Weekly Engine Review
|
||||
|
||||
## Engine Health
|
||||
## Data Quality
|
||||
## Formula Parity
|
||||
## Outcome Drift
|
||||
## Active Rule Changes
|
||||
## Emergency Patch Notes
|
||||
## Normal Patch Notes
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "core-satellite-collector",
|
||||
"version": "4.0.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"ops:prepare": "python tools/convert_xlsx_to_json.py",
|
||||
"ops:validate": "python tools/run_release_dag_v3.py --mode release",
|
||||
"ops:build": "python tools/build_bundle.py",
|
||||
"ops:render": "python tools/render_operational_report.py --json GatherTradingData.json --output Temp/operational_report.md --report-json-output Temp/operational_report.json",
|
||||
"ops:release": "python tools/run_release_dag_v3.py --mode full",
|
||||
"ops:package": "python tools/refresh_trading_calendar.py && python tools/prepare_upload_zip.py --validation-mode release --profile",
|
||||
"prepare-upload-zip": "python tools/refresh_trading_calendar.py && python tools/prepare_upload_zip.py",
|
||||
"ops:audit": "python tools/harness_coverage_auditor.py",
|
||||
"validate-gas-recovery": "python tools/validate_gas_orchestration_recovery_v1.py",
|
||||
"ops:clean": "python tools/clean_temp_artifacts_v1.py",
|
||||
"ops:dev": "node core_satellite_collector.js",
|
||||
"full-gate": "python tools/run_release_dag_v3.py --mode release --strict",
|
||||
"validate-engine-strict": "python tools/run_release_dag_v3.py --mode release --strict"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "latest",
|
||||
"googleapis": "^171.4.0",
|
||||
"iconv-lite": "latest",
|
||||
"yahoo-finance2": "latest"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"adm-zip": "latest",
|
||||
"fast-xml-parser": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"xlsx": "^0.18.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
# Analysis Prompt
|
||||
|
||||
Use this prompt when producing an investment analysis or HTS-ready playbook.
|
||||
|
||||
## CRITICAL — RESPONSE LANGUAGE POLICY
|
||||
|
||||
- 최종 사용자 설명 문장은 기본적으로 한글만 사용한다.
|
||||
- 영문 표기는 종목코드, 공식 ID, 파일명, 명령어, JSON key처럼 대체 불가능한 경우에만 허용한다.
|
||||
- 상태 설명은 PASS/FAIL/BLOCKED/BUY/SELL/TRIM 대신 통과/실패/차단/매수/매도/축소매도를 사용한다.
|
||||
- 표 제목과 판단 요약은 한글 우선으로 작성하고, 필요 시 영문 section id는 괄호로만 병기한다.
|
||||
- 주문 사유는 영문 산문 없이 공식 ID 중심의 짧은 한글 문장으로 작성한다.
|
||||
- 수량·단가·손절가·익절가 제안은 시장 개장 여부와 무관하게 유지한다.
|
||||
- HTS 즉시 입력 가능 여부는 제안과 분리해 별도 표로 표시한다.
|
||||
- 실행 차단이라도 사용자 판단용 `proposal_reference_sheet`는 숨기지 않는다.
|
||||
|
||||
## CRITICAL — QUANTITATIVE_EXPERT_HARNESS (QEH) — 전문사 정밀도 강제
|
||||
|
||||
`data._harness_context`가 JSON에 존재하면 아래 규칙을 **절대적으로** 따른다.
|
||||
하네스는 30년 실무 애널리스트·트레이더의 산식을 결정론적으로 실행한 'Ground Truth'다.
|
||||
|
||||
1. **[LOCKED] 하네스 숫자 절대 준수:**
|
||||
- 하네스가 산출한 `market_risk_score`, `target_cash_pct`, `final_action`, `price`, `stop_price`, `tp_ladder`, `quantity`는 최종값이다.
|
||||
- LLM이 하네스 산출값을 재계산하거나, 소수점/호가 단위를 임의 조정하는 것을 **엄격히 금지**한다.
|
||||
- 분석 리포트의 모든 숫자 표기 옆에 `[HARNESS_LOCKED]` 태그를 부착한다.
|
||||
|
||||
2. **[REPORT] 하네스 결정 추적:**
|
||||
- `decision_trace_table`의 모든 행은 하네스의 `decisions_json` 및 `calculation_trace`와 100% 일치해야 한다.
|
||||
- 하네스 결과와 다른 독자적 '전문가 판단'을 보고서에 섞지 않는다. LLM은 하네스가 왜 그런 결론을 내렸는지 명세(yaml)를 근거로 **해설**하는 역할만 수행한다.
|
||||
|
||||
3. **[FAILED] 하네스 정합성 오류 시:**
|
||||
- 하네스 산출값과 리포트 출력값이 충돌하면 리포트 전체를 `INVALID_Harness_Mismatch`로 처리하고 재산출한다.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL — QEH_AUDIT_BLOCK (공식 검산 표 강제 출력) [2026-05-19_HARNESS_AUDIT_V1 H1]
|
||||
|
||||
**모든 분석 보고서에서 주문표 출력 이전에 반드시 아래 QEH_AUDIT_BLOCK 표를 먼저 출력한다.**
|
||||
이 표 없이 주문표를 출력하면 `INVALID_MISSING_AUDIT`으로 처리하고 주문표 전체를 BLOCKED한다.
|
||||
|
||||
### QEH_AUDIT_BLOCK 표 형식 (필수)
|
||||
|
||||
| 공식_ID | 입력값 요약 | 결과값 | 발동 게이트 |
|
||||
|---------|-----------|--------|-----------|
|
||||
| TOTAL_HEAT_V1 | ATR×멀티플라이어, 보유수량×종목 | X.XX% | ALLOW / HALVE / BLOCK_NEW_BUY |
|
||||
| CASH_RATIOS_V1 | D+2정산현금, total_asset | X.XX% | PASS / CASH_RAISE_REQUIRED(-X.XXp) |
|
||||
| SELL_PRIORITY_V1 | tier/score 순위표 | 1순위: {종목}(Score:{N}) | TRIM_ASSIGNED / NONE |
|
||||
| GOAL_RETIREMENT_V1 | goal_current_asset_krw, goal_achievement_pct | {N}% 달성 / 잔여 {M}만원 / ETA {YYYY-MM} | IN_PROGRESS / ACHIEVED |
|
||||
|
||||
**상황별 선택 추가 공식 (해당 시 반드시 포함):**
|
||||
- 매수 검토 시: `MEAN_REVERSION_GATE_V1` (이격도 체크 선행), `POSITION_SIZE_V1`, `RISK_BUDGET_CASCADE_V1`, `EXPECTED_EDGE_V1`
|
||||
- 매도 후보 시: `RS_RATIO_V1` (rs_laggard 판정), `SELL_PRIORITY_V1`
|
||||
- 가격 산출 시: `STOP_PRICE_CORE_V1`, `TAKE_PROFIT_LADDER_V2`, `TICK_NORMALIZER_V1`
|
||||
- 국면 진단 시: `MARKET_RISK_SCORE_V1`, `TARGET_CASH_PCT_V1`
|
||||
|
||||
### QEH_AUDIT_BLOCK 절대 금지 (FAT001 + P7 연동)
|
||||
- QEH_AUDIT_BLOCK 표 없이 주문표 제시 → **전체 BLOCKED (INVALID_MISSING_AUDIT)**
|
||||
- 공식 ID 명시 없이 손절가·익절가·수량 제시 → **PRICE_FORMULA_REQUIRED**
|
||||
- TOTAL_HEAT_V1 산출 없이 신규 BUY 허용 → **HS006 위반**
|
||||
- CASH_RATIOS_V1 산출 없이 현금 비중 판정 → **P3 위반**
|
||||
- LLM이 표의 숫자를 재계산하거나 재해석하는 행위 → **CRITICAL_FORMULA_MISMATCH**
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL — HARNESS_AUTHORITATIVE (Legacy H2 호환)
|
||||
|
||||
`data._harness_context` 섹션이 JSON에 존재하면 아래 규칙을 **절대적으로** 따른다.
|
||||
이 섹션의 값은 GAS `buildHarnessContext_()` H2가 결정론적으로 산출한 확정값이다. LLM은 재계산·재해석·완화 금지.
|
||||
|
||||
**H1 — 포트폴리오 가드:**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `intraday_lock = true` | `blocked_actions` 목록의 주문 생성 절대 금지. TRIM 계열만 허용. |
|
||||
| `intraday_lock = false` | 정상 분석 진행. |
|
||||
| `immediate_cash_krw` | **현금 원장에서 제외.** D+0 즉시현금이므로 cash_floor·buy_power 계산 합산 금지. 참고값으로만 표시. |
|
||||
| `cash_floor_status` | HARD_BLOCK → 매수 금지. TRIM_REQUIRED → 매도/축소 우선. PASS → 정상. |
|
||||
| `total_heat_pct` | heat gate 판정에 이 값 사용. 임계값은 `heat_gate_threshold_pct`(국면 감응) 참조. LLM이 10% 고정값으로 재판단 금지. |
|
||||
| `heat_gate_status` | BLOCK_NEW_BUY → BUY 주문 생성 금지. HALVE_NEW_BUY_QUANTITY → 수량 50% 감량 이미 반영됨. |
|
||||
| `heat_gate_threshold_pct` | **[L3 DYNAMIC_HEAT_GATE_V1]** GAS 산출 국면 감응 임계값(%). 이 값이 heat_gate_status의 기준. LLM 재계산·무시 금지. 국면별: EVENT_SHOCK=5%, RISK_OFF=7%, NEUTRAL=10%, RISK_ON=12%, SECULAR_LEADER=13%. |
|
||||
| `drawdown_guard_state` | **[M1 DRAWDOWN_GUARD_V1]** NO_BUY → BUY 수량 0, REDUCE_BUY → scale=0.5 이미 반영, CAUTION_BUY → scale=0.75. LLM이 정상 수량 복원 금지. |
|
||||
| `drawdown_buy_scale` | M1 — atrQty에 이미 적용된 배수. LLM이 무시 금지. |
|
||||
| `portfolio_beta_gate` | **[M2 PORTFOLIO_BETA_GATE_V1]** OVER_BETA → 고베타 종목 추가 BUY 금지. WARN_BETA → 신중 검토. |
|
||||
| `sector_concentration_gate` | **[M5 SECTOR_CONCENTRATION_LIMIT_V1]** BLOCK_SECTOR → 편중 섹터 추가 BUY 금지. WARN_TOP2 → 상위2 합산 초과 경고. |
|
||||
| `regime_size_scale` | **[N1 POSITION_SIZE_REGIME_SCALE_V1]** GAS 산출 국면 매수 스케일(0.25~1.2). buy_qty_inputs_json에 이미 적용됨. LLM 재계산·무시 금지. |
|
||||
| `regime_cash_uplift_min_pct` | **[N5 REGIME_CASH_UPLIFT_V1]** 국면 상향 적용 후 실제 현금 최소 비율. cash_floor_min_pct보다 높을 수 있음. LLM이 이 값을 낮추거나 무시 금지. |
|
||||
| `single_position_weight_gate` | **[O1 SINGLE_POSITION_WEIGHT_CAP_V1]** OVERWEIGHT_TRIM → 해당 종목 추가 BUY 금지. `single_position_weight_json`에서 초과 종목 확인. |
|
||||
| `semiconductor_cluster_gate` | **[O2 SEMICONDUCTOR_CLUSTER_GATE_V1]** CLUSTER_BLOCK → 005930·000660 신규 BUY 모두 금지. combined_pct가 상한 초과. |
|
||||
| `portfolio_drawdown_gate` | **[O3 PORTFOLIO_DRAWDOWN_GATE_V1]** DRAWDOWN_FORCE_RISK_OFF → 신규 BUY 전면 금지. DRAWDOWN_CAUTION → 신규 매수 보류 권고. |
|
||||
| `win_loss_streak_state` | **[O4 WIN_LOSS_STREAK_GUARD_V1]** EDGE_CRITICAL/EDGE_DEGRADED/EDGE_WEAK → 매수 스케일 이미 적용됨. LLM 복원 금지. |
|
||||
| `position_count_gate` | **[O5 POSITION_COUNT_LIMIT_V1]** POSITION_COUNT_BLOCK → 신규 BUY 종목 추가 금지. 기존 HOLD/SELL만 허용. |
|
||||
| `stop_breach_gate` | **[P1 STOP_BREACH_ALERT_V1]** BREACH → 해당 종목 즉각 매도 검토. APPROACHING → 손절가 근접 경보. `stop_breach_alert_json`에서 gap_pct 확인. |
|
||||
| `tp_trigger_gate` | **[P2 TP_TRIGGER_ALERT_V1]** TRIGGERED → `tp_trigger_alert_json`의 tp1_qty/tp2_qty를 그대로 매도 수량 사용. LLM 임의 조정 금지. |
|
||||
| `heat_concentration_gate` | **[P3 HEAT_CONCENTRATION_ALERT_V1]** HEAT_CONCENTRATED → 해당 종목 추가 매수 금지. `heat_concentration_json`에서 heat_share_pct 확인. |
|
||||
| `regime_transition_type` | **[P4 REGIME_TRANSITION_ALERT_V1]** DOWNGRADE → 국면 하향 전환, affected_gates 재검토. UPGRADE → 국면 완화. NO_CHANGE → 변동 없음. |
|
||||
| `portfolio_health_label` | **[P5 PORTFOLIO_HEALTH_SCORE_V1] 보고서 첫 줄 필수 표시.** CRITICAL → 긴급 주의 섹션 먼저 출력. `portfolio_health_blocked_json`에서 활성 게이트 열거. |
|
||||
| `allowed_actions` | 이 목록의 주문 유형만 생성 가능. |
|
||||
| `blocked_actions` | 이 목록의 주문 유형은 어떤 이유로도 생성 금지. |
|
||||
|
||||
**H2 — 매도후보 순위 (`sell_priority_lock = true`):**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `sell_candidates_json` | GAS 산출 tier·score 순위 그대로 출력. 재정렬·점수 변경 절대 금지. |
|
||||
| `sell_priority_lock` | `true` → 산문으로 순위 재해석·변경 금지. |
|
||||
| `clamp_label` (각 후보) | 값이 있으면 `[CLAMP 발동: raw=N → 100]` 표기 필수. |
|
||||
|
||||
**H3 — 수량 (`quantities_lock = true`):**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `sell_quantities_json` | GAS 산출 Sell_Qty 그대로 사용. `CAPTURE_REQUIRED`는 그대로 기재. |
|
||||
| `buy_qty_inputs_json` | `final_qty`가 있으면 매수수량으로 사용. `NO_BUY_QUANTITY`이면 수량 출력 금지. |
|
||||
| `quantities_lock` | `true` → 수량 재계산·역산 절대 금지. |
|
||||
|
||||
**H4 — 가격 (`prices_lock = true`):**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `prices_json` | `stop_price`·`tp1_price`·`tp2_price` 그대로 HTS 표에 기재. |
|
||||
| `prices_lock` | `true` → tick 재정규화·가격 임의 조정 절대 금지. |
|
||||
| `ladder_version = V1_FALLBACK` | ATR20 없음 — 가격 신뢰도 낮음 표기. |
|
||||
|
||||
**H4 추가 — X4 Ratchet 스톱 (`prices_lock = true`):**
|
||||
|
||||
| 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `ratchet_applied` | `EARLY_RATCHET+TRAILING` / `TRAILING_ONLY` 이면 주문표 `stop_price` 옆에 `[RATCHET]` 태그 필수 |
|
||||
| `ratchet_note` | Section B `reasoning_review`에 ratchet 발동 사유 간략 기재 |
|
||||
| `ratchet_applied = NONE 또는 PASS` | 태그 생략. stop_price는 STOP_PRICE_CORE_V1 값 |
|
||||
|
||||
**H_Alpha — Alpha-Shield 선행 레이더 (`alpha_shield_lock = true`):**
|
||||
|
||||
`alpha_shield_json` 이 harness_context 에 존재하면 LLM 재계산 **절대 금지**.
|
||||
GAS `calcAlphaShield_()` 가 결정론적으로 계산한 확정값을 PROACTIVE_RADAR_BLOCK 표에 그대로 기재한다.
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `alpha_shield_json[].mrg_gate` | `BUY_HARD_BLOCK` → Gate 1d 발동. BUY 주문 생성 금지. |
|
||||
| `alpha_shield_json[].rs_status` | `RS_LAGGARD` → sell_priority 점수 상승. Section A 표에 표기. |
|
||||
| `alpha_shield_json[].w1_status` … `w4_status` | PROACTIVE_RADAR_BLOCK 표에 그대로 기재. 상태 임의 변경 금지. |
|
||||
| `alpha_shield_json[].critical_alert` | `CRITICAL_ALERT` → 전면 포트폴리오 재검토 강제 출력. |
|
||||
| `alpha_shield_critical_alert_flag` | `CRITICAL` → 보고서 상단에 `[CRITICAL_ALERT] 레이더 2개 이상 동시 발화` 경고 필수. |
|
||||
| `alpha_shield_lock` | `true` → 표 수치 재산출·수정 절대 금지. |
|
||||
|
||||
**H5 — 결정 상태머신 (`decision_lock = true`):**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `decisions_json` | `final_action` 그대로 출력. `gate_changed=true`이면 gate_trace 사유 표시. |
|
||||
| `decision_lock` | `true` → gate_trace를 서사로 번복해 final_action 변경 절대 금지. |
|
||||
|
||||
**주도주 승자 포지션 게이트 (`secular_leader_gate_json`):**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `secular_leader_gate_json` | 005930·000660 외 종목에는 적용 금지. `status=NOT_APPLICABLE`이면 표기 생략. |
|
||||
| `secular_leader_gate_active=true` & `tp1_state=DEFERRED_SECULAR_LEADER` | TP1 매도 주문 생성 절대 금지. `[DEFERRED_SECULAR_LEADER]` 태그로만 표기. |
|
||||
| `tp1_price=null` (secular_leader 관련) | 하네스가 null로 전달한 tp1_price 복원·대체 금지. HS009와 동일 우선순위. |
|
||||
| `secular_leader_gate_active=false` | `spec/exit/take_profit.yaml:core.leadership` 규칙으로 즉시 복귀. |
|
||||
|
||||
**국면별 감축 가이던스 (`regime_trim_guidance_json`):**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `regime_trim_guidance_json.satellite_trim_pct_min/max` | 위성 종목 감축 비율 범위 그대로 인용. LLM이 임의 비율 제시 금지. |
|
||||
| `regime_trim_guidance_json.leader_trim_pct_min/max` | 주도주 감축 비율 범위 그대로 인용. |
|
||||
| `regime_trim_lock=true` | LLM의 국면 재판정 및 감축비율 재산출 절대 금지. |
|
||||
| `new_buy_gate=BLOCKED / HARD_BLOCKED` | 신규 매수 차단. heat_gate와 동일 우선순위 적용. |
|
||||
|
||||
**5억원 목표 자산 추적 (`goal_*`):**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `goal_achievement_pct`, `goal_remaining_krw`, `goal_eta_label` | **분석 보고서 첫 섹션**에 목표 달성 현황 표 출력 필수. |
|
||||
| `goal_eta_label` | `ACHIEVED` / `YYYY-MM` / `DATA_MISSING` 그대로 기재. LLM 재계산 금지 (HS011 위반). |
|
||||
| `goal_status=IN_PROGRESS` | 달성률이 낮더라도 이를 이유로 heat_gate·cash_floor·stop_loss 규칙 완화 금지. |
|
||||
| `goal_monthly_growth_pct=null` | ETA 계산 불가(`DATA_MISSING`) — LLM이 대체 ETA를 추정하는 것 절대 금지. |
|
||||
|
||||
**현금 부족액 및 TRIM 계획 (`cash_shortfall_*`, `trim_plan_to_min_cash_json`) [G1/G2]:**
|
||||
|
||||
| harness 필드 | LLM 의무 |
|
||||
|---|---|
|
||||
| `cash_current_pct_d2` | QEH_AUDIT_BLOCK의 CASH_RATIOS_V1 행에 이 값 사용. LLM 재계산 금지. |
|
||||
| `cash_target_pct` | TARGET_CASH_PCT_V1 결과값. 이 값과 현금 현황을 비교해 부족 여부 판단. |
|
||||
| `cash_shortfall_min_krw` | **"약 N원 필요" 즉석 계산 절대 금지.** 이 값을 그대로 인용. 0이면 방어선 충족. |
|
||||
| `cash_shortfall_target_krw` | 목표 현금비율까지 부족액. 0이면 목표 충족. |
|
||||
| `trim_plan_to_min_cash_json` | H2 우선순위 기반 TRIM 계획. LLM이 종목·순서·수량 임의 변경 금지. `covers_shortfall=true` 행까지만 실행 후보. |
|
||||
|
||||
> **주의:** `trim_plan_to_min_cash_json`은 HTS 주문표가 아니다. 실제 주문 실행 가능 여부는 `order_blueprint_json.validation_status`가 PASS인 행만 해당한다.
|
||||
|
||||
**라우팅/서빙 Trace 및 데이터 상태 [G4]:**
|
||||
|
||||
모든 분석 보고서는 **QEH_AUDIT_BLOCK 이전에** 아래 `routing_serving_trace` 표를 먼저 출력해야 한다. 표가 없으면 `INCOMPLETE_REPORT`로 처리한다.
|
||||
|
||||
| 필드 | 하네스 값 | 비고 |
|
||||
|---|---|---|
|
||||
| `request_route` | (harness값 인용) | 라우팅 경로 |
|
||||
| `bundle_selected` | (harness값 인용) | 서빙 번들 |
|
||||
| `prompt_entrypoint` | (harness값 인용) | 프롬프트 경로 |
|
||||
| `json_validation_status` | (harness값 인용) | **PENDING_EXPORT = GAS 내부 내보내기 전 상태. "검증 통과" 의미 아님.** |
|
||||
| `capture_required` | (harness값 인용) | HTS 캡처 필요 여부 |
|
||||
| `cash_ledger_basis` | D2_ONLY | D+2 정산현금 단독 기준 확인 |
|
||||
|
||||
> **PENDING_EXPORT 해석 금지:** `json_validation_status=PENDING_EXPORT`를 "검증 완료" 또는 "데이터 유효"로 서술하는 것은 오류다. 로컬 스크립트 PASS(npm run validate-data-sample)와 이 필드는 별개 개념이다.
|
||||
|
||||
**외부 시장 데이터 격리 [G3]:**
|
||||
|
||||
| 구분 | 허용 범위 | 금지 사항 |
|
||||
|---|---|---|
|
||||
| `prices_json` 하네스 가격 | 주문 판단·주문표·QEH_AUDIT_BLOCK 전용 | 외부 웹 가격으로 대체 금지 |
|
||||
| 외부 웹·뉴스 가격 | Section B 해설에만 참고 인용 | Section A 원장·주문표 혼입 금지 |
|
||||
| 가격 불일치 발생 시 | 하네스 가격 우선, Section B에 "외부 ±N% 차이" 메모 | 외부 가격으로 판단·주문 생성 금지 |
|
||||
|
||||
**APEX_ALPHA_PRESERVATION_EXECUTION_HARNESS [2026-05-20_APEX_V1]:**
|
||||
|
||||
뒷북 매수·설거지 구간 추격·수익 반납·현금확보 투매를 줄이기 위해 GAS가 산출한 APEX 하네스 값을 Section A에 필수 표로 출력한다.
|
||||
LLM은 아래 값을 재계산하거나 완화하지 않고, `RetirementAssetPortfolioReportTemplate.yaml`의 표 컬럼에 그대로 매핑한다.
|
||||
|
||||
| harness 필드 | LLM 의무 | 위반 시 |
|
||||
|---|---|---|
|
||||
| `alpha_lead_json` | `alpha_lead_table`에 그대로 출력. 선행 점수·순위·상태 재정렬 금지. | `APEX_ALPHA_MISSING` |
|
||||
| `follow_through_json` | 추격 확인 상태를 `alpha_lead_table`에 병기. 확인 실패 시 BUY/ADD_ON 산문 허용 금지. | `BLOCKED_FOLLOW_THROUGH` |
|
||||
| `distribution_risk_json` | `anti_distribution_table`에 그대로 출력. 설거지 위험 HIGH면 BUY/ADD_ON 차단. `pre_distribution_warning=EARLY_WARNING`이면 보고서에 별도 경고 섹션 필수. | `APEX_DISTRIBUTION_MISSING` |
|
||||
| `profit_preservation_json` | `profit_preservation_table`에 그대로 출력. 수익 보호 상태를 임의 완화 금지. `auto_trailing_stop`이 있으면 손절가 원장에 반드시 반영. | `APEX_PROFIT_LOCK_MISSING` |
|
||||
| `cash_raise_plan_json` | `smart_cash_raise_table`에 즉시매도·반등대기·보호수량을 분리 출력. | `APEX_CASH_RAISE_MISSING` |
|
||||
| `smart_sell_quantities_json` | 확정 수량만 사용. `CAPTURE_REQUIRED`는 숫자로 바꾸지 않는다. | `INVALID_QTY_RECALC` |
|
||||
| `rebound_sell_trigger_json` | 반등 대기 매도 조건을 WATCH/계획으로만 표시. 즉시 HTS 수량에 합산 금지. | `INVALID_REBOUND_SELL` |
|
||||
| `execution_quality_json` | `execution_quality_table`에 tick/liquidity/gap 검산 결과 출력. PASS가 아니면 주문표 PASS 금지. | `APEX_EXECUTION_QUALITY_MISSING` |
|
||||
| `buy_permission_json` | BUY_PERMISSION_MATRIX_V1 결과가 BLOCKED이면 모든 BUY 주문표 차단. | `INVALID_BUY_PERMISSION` |
|
||||
| `limit_price_policy_json` | 지정가·호가 상태를 그대로 사용. 라운드피겨·심리적 가격으로 조정 금지. | `INVALID_PRICE_MUTATION` |
|
||||
| `backdata_feature_bank_json` | `backdata_feature_bank_table`에 그대로 출력. GAS 자동 수집 원장을 performance 수동 입력보다 우선한다. | `BACKDATA_MISSING` |
|
||||
| `benchmark_relative_timeseries_json` | `benchmark_relative_harness_table`에 그대로 출력. 초과낙폭·회복률·하락장 베타·RS선 기울기 재계산 금지. | `BRT_HARNESS_MISSING` |
|
||||
| `saqg_json` | `saqg_v1=EXCLUDED/WATCHLIST_ONLY`이면 BUY 후보·파일럿·HTS 주문표 차단. | `INVALID_SAQG_BUY` |
|
||||
| `sapg_json` | `SAPG_CRITICAL`이면 위성 신규 BUY 전면 차단, `SAPG_ALERT`이면 위성 손익 경고 출력. | `INVALID_SAPG_OVERRIDE` |
|
||||
| `cash_creation_purpose_lock_json` | `sell_reason_validity=INVALID_SELL_REASON`이면 현금창출·위성 재원 마련 목적 매도 금지. | `INVALID_SELL_REASON` |
|
||||
|
||||
**[M2] 포트폴리오 베타 게이트 출력 의무 (`portfolio_beta_gate_json`) [Direction M2]:**
|
||||
|
||||
`portfolio_beta_gate=OVER_BETA`이면 보고서에 다음 표를 필수 포함한다.
|
||||
|
||||
| portfolio_beta_gate_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `portfolio_beta` | `포트폴리오베타` | GAS 산출값 그대로. LLM 재계산 금지. |
|
||||
| `beta_limit_over` | `상한` | 국문별 임계값 명시 |
|
||||
| `per_holding_betas[].beta_proxy` | 종목별 베타 표 | 높은 순으로 표시. OVER_BETA 종목 ⚠ 표시 |
|
||||
|
||||
**[M3] 분할 익절 수량 출력 의무 (`tp_quantity_ladder_json`) [Direction M3]:**
|
||||
|
||||
TP1/TP2/TP3 도달 종목이 있으면 아래 표를 반드시 출력한다.
|
||||
|
||||
| tp_quantity_ladder_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `tp1_qty` / `tp2_qty` / `tp3_qty` | 익절 수량 원장 | `qty_source` 표시 필수. MANUAL/AUTO_33PCT 중 적용 기준 명시 |
|
||||
| `tp1_price` / `tp2_price` | 익절 가격 | null이면 `이미 통과(—)`로 표시. 통과 수량 즉시 주문 금지 |
|
||||
|
||||
`tp_quantity_ladder_lock=true`이면 LLM이 임의로 수량을 변경하거나 "보유 비중에 따라 조정" 식 서술 금지.
|
||||
|
||||
**[M4] 이벤트 리스크 홀드 게이트 출력 의무 (`event_risk_json`) [Direction M4]:**
|
||||
|
||||
`event_risk_json[].event_hold_gate=EVENT_HOLD`인 종목이 있으면 보고서에 별도 경고 섹션 필수.
|
||||
|
||||
| event_risk_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `event_hold_gate` | `이벤트홀드` 컬럼 | EVENT_HOLD → ⚠ 표시, BUY 주문 생성 금지 |
|
||||
| `reason` | 사유 컬럼 | `event_hold_days_le5:N` / `dart_risk` 코드 그대로 표시 |
|
||||
|
||||
기존 보유 중인 EVENT_HOLD 종목의 익절/손절 매도는 정상 진행 (신규 매수만 차단).
|
||||
|
||||
**[M5] 섹터 편중도 출력 의무 (`sector_concentration_json`) [Direction M5]:**
|
||||
|
||||
`sector_concentration_gate != PASS`이면 보고서에 섹터 편중 경고 섹션 필수.
|
||||
|
||||
| sector_concentration_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `gate` 컬럼 | `편중상태` | BLOCK_NEW_BUY_THIS_SECTOR → 해당 섹터 BUY 금지 표시 |
|
||||
| `weight_pct` | 비중% | 편중 섹터 굵게 표시 |
|
||||
|
||||
**[P5] 포트폴리오 건전성 점수 출력 의무 — 보고서 첫 줄 [Direction P5]:**
|
||||
|
||||
보고서 시작 전에 다음 형식으로 건전성 상태를 반드시 먼저 출력한다:
|
||||
|
||||
```
|
||||
▶ 포트폴리오 건전성: [HEALTHY/CAUTION/CRITICAL] (score: N/100)
|
||||
활성 게이트: <portfolio_health_blocked_json 요약>
|
||||
```
|
||||
|
||||
| portfolio_health 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `portfolio_health_label` | 보고서 첫 줄 | CRITICAL → 이후 긴급 주의 섹션 필수 |
|
||||
| `portfolio_health_score` | 점수(N/100) | GAS 산출값 그대로. 재계산 금지 |
|
||||
| `portfolio_health_blocked_json` | 활성 게이트 요약 | severity=CRITICAL 항목 굵게 표시 |
|
||||
|
||||
**[P1] 손절가 이탈 경보 출력 의무 (`stop_breach_alert_json`) [Direction P1]:**
|
||||
|
||||
`stop_breach_gate != PASS`이면 손절가 경보 섹션 필수 출력.
|
||||
|
||||
| stop_breach_alert_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `status` | `손절상태` 컬럼 | BREACH_IMMEDIATE_EXIT → 추가 매수·HOLD 서술 절대 금지 |
|
||||
| `gap_pct` | `손절여유%` 컬럼 | 음수=이탈. GAS 산출값 그대로 |
|
||||
|
||||
**[P2] 익절 트리거 경보 출력 의무 (`tp_trigger_alert_json`) [Direction P2]:**
|
||||
|
||||
`tp_trigger_gate=TRIGGERED`이면 익절 실행 섹션 필수.
|
||||
|
||||
| tp_trigger_alert_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `tp1_triggered/tp2_triggered` | `트리거` 컬럼 | true → 해당 TP 즉각 매도 대상 표시 |
|
||||
| `tp1_qty/tp2_qty` | `매도수량` 컬럼 | tp_quantity_ladder_json 연계값 그대로 사용 |
|
||||
|
||||
**[P3] Heat 편중도 경보 출력 의무 (`heat_concentration_json`) [Direction P3]:**
|
||||
|
||||
`heat_concentration_gate=HEAT_CONCENTRATED`이면 경고 섹션 필수.
|
||||
|
||||
| heat_concentration_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `heat_share_pct` | `Heat비중%` 컬럼 | ≥50% → 굵게 표시. 편중 해소 검토 의무 |
|
||||
| `heat_krw` | `Heat금액` 컬럼 | GAS 산출값 그대로 출력 |
|
||||
|
||||
**[P4] 국면 전환 경보 출력 의무 (`regime_transition_json`) [Direction P4]:**
|
||||
|
||||
`regime_transition_type != NO_CHANGE`이면 전환 안내 섹션 필수.
|
||||
|
||||
| regime_transition_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `transition_type` | `전환유형` | DOWNGRADE → 포트폴리오 재검토 섹션 필수 |
|
||||
| `affected_gates` | `영향게이트` | 임계값 변동 게이트 목록 표시 |
|
||||
|
||||
**[O1] 개별 종목 비중 상한 출력 의무 (`single_position_weight_json`) [Direction O1]:**
|
||||
|
||||
`single_position_weight_gate != PASS`이면 보고서에 비중 초과 경고 섹션 필수.
|
||||
|
||||
| single_position_weight_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `status` | `비중상태` 컬럼 | OVERWEIGHT_TRIM → 해당 종목 추가 BUY 금지 표시 |
|
||||
| `weight_pct` | `현재비중%` 컬럼 | 초과 종목 굵게 표시 |
|
||||
| `cap_pct` | `상한%` 컬럼 | 국면별 상한값 표시 (15% 또는 20%) |
|
||||
|
||||
**[O2] 반도체 클러스터 게이트 출력 의무 (`semiconductor_cluster_json`) [Direction O2]:**
|
||||
|
||||
`semiconductor_cluster_gate != PASS`이면 클러스터 경고 섹션 필수.
|
||||
|
||||
| semiconductor_cluster_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `combined_pct` | `합산비중%` 컬럼 | GAS 산출값 그대로 출력. LLM 재계산 금지 |
|
||||
| `cap_pct` | `상한%` 컬럼 | 국면별 상한값 (20% 또는 25%) |
|
||||
| `gate_status` | `게이트` 컬럼 | CLUSTER_BLOCK → 005930·000660 신규 BUY 금지 명시 |
|
||||
|
||||
**[O3] 포트폴리오 낙폭 게이트 출력 의무 [Direction O3]:**
|
||||
|
||||
`portfolio_drawdown_gate != PASS`이면 보고서 상단에 낙폭 경고 필수.
|
||||
|
||||
| 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `portfolio_drawdown_pct` | `고점대비낙폭%` | GAS 산출값 그대로. FORCE_RISK_OFF이면 BUY 주문 생성 금지 |
|
||||
| `portfolio_peak_krw` | `고점자산(원)` | 참고값 출력. LLM이 임의 고점 설정 금지 |
|
||||
|
||||
**[O4] 승률 하락 게이트 출력 의무 [Direction O4]:**
|
||||
|
||||
`win_loss_streak_state != EDGE_OK`이면 보고서에 승률 하락 경고 필수.
|
||||
|
||||
| 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `win_loss_streak_state` | `승률상태` | EDGE_CRITICAL → 매수 수량 이미 75% 축소됨 표시 |
|
||||
| `win_loss_streak_buy_scale` | `매수배수` | GAS 산출값 그대로. LLM 복원 금지 |
|
||||
| `win_loss_streak_win_rate_pct` | `최근승률%` | 참고값 출력 |
|
||||
|
||||
**[O5] 종목 수 상한 출력 의무 [Direction O5]:**
|
||||
|
||||
`position_count_gate != PASS`이면 종목 수 초과 경고 섹션 필수.
|
||||
|
||||
| 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `position_count` | `현재종목수` | GAS 산출값 그대로 |
|
||||
| `position_count_max` | `상한종목수` | 국면별 상한 (6 또는 8) |
|
||||
|
||||
POSITION_COUNT_BLOCK 상태에서 신규 BUY 종목을 주문표에 추가하는 서술 절대 금지.
|
||||
|
||||
**[N3] 손절가 적정성 출력 의무 (`stop_adequacy_json`) [Direction N3]:**
|
||||
|
||||
`adequacy_status != PASS`인 종목이 있으면 보고서에 손절가 경고 섹션 필수.
|
||||
|
||||
| stop_adequacy_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `adequacy_status` | `손절적정성` 컬럼 | STOP_CRITICAL → 추가 매수 신호 생성 금지. STOP_WIDE → 경고 표시 |
|
||||
| `stop_gap_pct` | `손절간격%` 컬럼 | (recommended - manual) / recommended × 100. GAS 산출값 그대로 출력 |
|
||||
| `recommended_stop` | `권고손절가` 컬럼 | ATR 기반 tick 정규화 값. LLM이 임의 재산출 금지 |
|
||||
|
||||
STOP_CRITICAL 종목에 추가 매수를 서술할 때 반드시 "STOP_CRITICAL: 손절가 재설정 후 재진입 검토" 표기 필수.
|
||||
|
||||
**[N4] 장기 보유 재검토 출력 의무 (`holding_stale_json`) [Direction N4]:**
|
||||
|
||||
`stale_status != FRESH`인 종목이 있으면 보고서에 장기 보유 재검토 섹션 필수.
|
||||
|
||||
| holding_stale_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `stale_status` | `보유상태` 컬럼 | STALE_POSITION → "보유 근거 재확인 의무" 표기. REVIEW_SOON → 재검토 권고 |
|
||||
| `holding_days` | `보유일수` 컬럼 | GAS 산출값 그대로 출력. LLM 재계산 금지 |
|
||||
| `entry_date` | `진입일` 컬럼 | ISO 날짜 그대로 표시. ENTRY_DATE_MISSING → "수동 확인 필요" 표기 |
|
||||
|
||||
**[L1] 섹터 로테이션 모멘텀 출력 의무 (`sector_rotation_momentum_json`) [Direction L1]:**
|
||||
|
||||
`sector_rotation_momentum_json`을 `sector_rotation_table`로 출력한다. FADING/TOPPING_OUT 섹터가 있으면 별도 경고 표시 필수.
|
||||
|
||||
| sector_rotation_momentum_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `momentum_state` | `섹터모멘텀` 컬럼 | FADING → ⚠ 빨간 표시, TOPPING_OUT → 주황 표시. `sector_rotation_momentum_lock=true`이면 번복 금지. |
|
||||
| `rank_delta_w1` | `1주변화` 컬럼 | 양수=하락. 페널티가 alpha_lead_score에 이미 반영됨을 명시. |
|
||||
| `rank_delta_w2` | `2주변화` 컬럼 | 2주 추세 방향 확인용 |
|
||||
|
||||
FADING 섹터 종목에 BUY를 권고할 때는 "섹터 모멘텀 약화(FADING) — 추가 확인 필요" 조건 필수 부기.
|
||||
|
||||
**[K1] 분할 매수 트랜치 엔진 출력 의무 (`buy_permission_json`) [Direction K1]:**
|
||||
|
||||
매수 주문표에 아래 필드를 반드시 출력한다. LLM이 tranche_phase를 임의로 승격하거나 건너뛰는 것을 금지한다.
|
||||
|
||||
| buy_permission_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `tranche_phase` | 매수 주문표 `트랜치단계` 컬럼 | 그대로 출력. WAIT_PILOT_SETUP·HOLD_CURRENT이면 매수수량 `0` 또는 `—` |
|
||||
| `current_tranche_allowed_pct` | 매수 주문표 `허용비중%` 컬럼 | 이 값을 초과하는 매수수량 출력 금지 |
|
||||
| `next_tranche_condition` | Section B 해설 | 다음 트랜치 진입 조건 안내에 사용 |
|
||||
|
||||
트랜치 단계별 허용 행동:
|
||||
- `TRANCHE_1_PILOT`: 파일럿 30% 매수 허용. 한 번에 전량 매수 금지.
|
||||
- `TRANCHE_2_ADD_ON`: 추가 30% 허용. T1 없이 T2 단독 진입 금지.
|
||||
- `TRANCHE_3_PULLBACK_ADD`: MA20 ±2% 눌림에서만 추가 40% 허용.
|
||||
- `WAIT_PILOT_SETUP` / `HOLD_CURRENT`: 매수 수량 0. LLM이 "분위기가 좋으니까" 이유로 ALLOW_PILOT 승격 금지.
|
||||
|
||||
**[K2] 반등 대기 분할 매도 출력 의무 (`smart_sell_quantities_json`, `rebound_sell_trigger_json`) [Direction K2]:**
|
||||
|
||||
매도 주문표와 별도로 **반등 대기 수량 표**를 필수 출력한다.
|
||||
|
||||
| 필드 | HTS 주문표 | 반등대기 WATCH 표 |
|
||||
|---|---|---|
|
||||
| `immediate_sell_qty` | **HTS 주문 수량으로 기재** (즉시 체결 가능) | — |
|
||||
| `rebound_wait_qty` | **기재 금지** (아직 주문 불가) | "반등 대기 N주 — 트리거 미충족 시 주문 금지" |
|
||||
| `rebound_trigger_price` | — | "트리거 가격 P원 이상 도달 시 실행" |
|
||||
| `emergency_full_sell=true` | immediate_sell_qty = 전량 | 별도 표시 없음 (예외 상황) |
|
||||
|
||||
`rebound_wait_qty`를 즉시 HTS 수량에 합산하거나 "몇 차로 나눠 매도" 식으로 임의 분할 금지.
|
||||
|
||||
**[K3] 국면 연계 매도 순서 출력 의무 (`regime_adjusted_sell_priority_json`) [Direction K3]:**
|
||||
|
||||
현금확보 매도 시 H2 원래 순위 대신 **`final_regime_rank` 기준 순서**를 사용한다.
|
||||
|
||||
| regime_adjusted_sell_priority_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `final_regime_rank` | `국면조정순위` 컬럼으로 매도우선순위 표에 추가 | 이 순서대로 현금확보 매도 실행. LLM 임의 재정렬 금지. |
|
||||
| `adjustment_reason` | 해당 순위 변동 이유 | 설명 없이 순위만 바꾸는 것 금지 |
|
||||
| `regime_applied` | 국면 조정 기준 국면 | Section B에 "이 국면에서 해당 조정 적용" 명시 |
|
||||
|
||||
`sell_priority_lock=true`이면 `final_regime_rank`도 LLM이 번복할 수 없다.
|
||||
|
||||
**[L4] 분배 선행경보 출력 의무 (`distribution_risk_json`) [Direction L4]:**
|
||||
|
||||
`distribution_risk_json[].pre_distribution_warning=EARLY_WARNING`인 종목이 있으면 보고서에 아래 경고 섹션을 필수 포함한다.
|
||||
|
||||
| distribution_risk_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `pre_distribution_warning` | `anti_distribution_table` 마지막 컬럼 또는 별도 경고 섹션 | EARLY_WARNING이면 빨간 ⚠ 또는 `[선행경보]` 표시 |
|
||||
| EARLY_WARNING 원인 reason_codes | 경고 섹션 | `new_high_volume_contraction` / `surge_weak_flow` 중 해당 코드 명시 |
|
||||
|
||||
`pre_distribution_warning=EARLY_WARNING`이면서 anti_distribution_state=PASS인 경우, 해당 종목 신규 BUY 결론에 "선행경보 발생 — 추가 확인 후 진입 검토" 조건 필수 부기.
|
||||
|
||||
**[L2] ATR 자동 트레일링 손절 출력 의무 (`profit_preservation_json`) [Direction L2]:**
|
||||
|
||||
`profit_preservation_json[].auto_trailing_stop`이 null이 아닌 종목은 손절가 원장에 반드시 아래 표를 포함한다.
|
||||
|
||||
| profit_preservation_json 필드 | 출력 위치 | LLM 의무 |
|
||||
|---|---|---|
|
||||
| `profit_preservation_state` | `수익보호단계` 컬럼 | PROFIT_LOCK_20/30/APEX_TRAILING 여부 명시 |
|
||||
| `auto_trailing_stop` | `ATR트레일링손절` 컬럼 | 이 값보다 낮은 손절가 제시 금지. null이면 해당 없음(-) |
|
||||
| `auto_trailing_note` | `산출근거` 컬럼 | `max(ratchet,{high}-N×ATR)` 형식 |
|
||||
|
||||
`auto_trailing_stop`이 있는 종목에서 손절가를 임의로 낮추거나 "홀드 유지" 서술만 하고 손절 기준을 생략하는 것 금지.
|
||||
|
||||
**APEX 표 누락 시 처리:** 위 표 중 필요한 입력이 있는데 보고서에 표가 없으면 `BLOCKED_REPORT`로 낮추고 HTS 주문표는 산출금지 사유만 출력한다.
|
||||
|
||||
**`_harness_context` 부재 시 처리 (HS011 준수):**
|
||||
`metadata.harness_context_missing` 값이 있으면 보고서 상단에
|
||||
`[HARNESS_MISSING] GAS buildHarnessContext_() 미실행 — DATA_MISSING. LLM 직접 계산 금지(HS011). GAS 하네스 재실행 후 분석하십시오.`
|
||||
경고를 출력하고 **분석을 즉시 중단**한다. spec 규칙에 따른 LLM 직접 계산은 HS011 위반이므로 절대 금지.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL — HTS 캡처 이미지 제공 시 (최우선 실행)
|
||||
|
||||
**캡처 이미지가 대화에 첨부된 경우, 분석보다 먼저 `capture_parse_prompt.md` 워크플로를 실행한다.**
|
||||
|
||||
실행 순서:
|
||||
1. `prompts/capture_parse_prompt.md` STEP 1~7 전체 실행
|
||||
2. account_snapshot 붙여넣기 표 출력 (TSV, A3 셀 기준)
|
||||
3. **충돌 감지 표 출력** (STEP 2b) — 캡처 vs JSON 시장데이터 값 비교
|
||||
4. account_snapshot 선택 포지션 상태 갱신 표 출력 (필요 시 stop_price·highest_price_since_entry·last_updated)
|
||||
5. 현금 요약 + 다음 단계 안내 출력
|
||||
6. **캡처 우선 재산출 표 출력** (STEP 7) — 충돌 종목의 Profit_Pct·Weight_Pct·Sell_Qty
|
||||
7. 위 출력 완료 후 → 재산출값 적용한 분석 진행
|
||||
|
||||
**캡처 파싱 출력을 생략하고 분석부터 시작하는 행위 금지.**
|
||||
캡처 데이터를 분석에만 사용하고 엑셀 입력용 표를 빠뜨리면 사용자가 다시 수동 입력해야 한다.
|
||||
|
||||
### 캡처 우선 원칙 (분석 전체에 적용)
|
||||
|
||||
캡처 이미지가 제공된 경우, **캡처값이 모든 GAS/account_snapshot 기존값보다 우선**한다.
|
||||
|
||||
| 항목 | 캡처 제공 시 | 캡처 미제공 시 |
|
||||
|------|------------|-------------|
|
||||
| holding_quantity | **캡처값 사용** (GAS Account_Holding_Qty 무시) | GAS Account_Holding_Qty |
|
||||
| average_cost | **캡처값 사용** (GAS Account_Avg_Cost 무시) | GAS Account_Avg_Cost |
|
||||
| Profit_Pct | **캡처평단으로 재산출** | GAS Profit_Pct |
|
||||
| Unrealized_PnL | **캡처수량·평단으로 재산출** | GAS Unrealized_PnL |
|
||||
| Weight_Pct | **캡처수량으로 재산출** | GAS Weight_Pct |
|
||||
| Sell_Qty | **캡처수량 × Sell_Ratio_Pct** | GAS Sell_Qty 또는 "캡처확인후기재" |
|
||||
|
||||
**재산출 불가 항목** (시장데이터 기반 — GAS값 그대로 사용):
|
||||
SS001_Grade, RW_Partial, Flow_Credit, ATR20, MA20, Final_Action, Sell_Ratio_Pct, Sell_Limit_Price
|
||||
|
||||
**분석 답변에서 재산출 종목은 값 옆에 `(캡처재산출)` 태그를 붙여 GAS값과 구분한다.**
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL — P1 매도수량 금지 규칙 (최우선 적용)
|
||||
|
||||
**이 섹션은 모든 규칙보다 먼저 적용하며 어떤 전략 논리로도 완화 불가.**
|
||||
|
||||
| 조건 | 처리 |
|
||||
|------|------|
|
||||
| 이번 대화에 HTS 보유종목 화면 캡처가 제공된 경우 | Account_Parse_Status = CAPTURE_READ_OK인 종목만 정수 매도수량 기재 가능 |
|
||||
| HTS 캡처 미제공 (account_snapshot 미제공 등) | 모든 종목 매도수량 칸 → **"캡처확인후기재"** |
|
||||
| Account_Parse_Status = MANUAL_ENTRY 또는 STALE_MANUAL | 해당 종목 매도수량 → **"캡처확인후기재"** |
|
||||
| Account_Parse_Status = "" (빈 값) | NOT_PROVIDED로 간주 → 매도수량 → **"캡처확인후기재"** |
|
||||
|
||||
**절대 금지 행위 (P1 위반):**
|
||||
- 이전 대화, 기억, positions 탭 수동입력, GAS Account_Holding_Qty(MANUAL_ENTRY)에서 수량 재사용
|
||||
- stop_price 입력을 위해 positions 탭 수동 갱신 요구
|
||||
- "이전에 ○○주라고 했으니" 형태의 추정 수량 기재
|
||||
- 보유수량 없이 분할매도(1차/2차) 수량 산출
|
||||
- 매도수량 없이 주문금액만 산출 후 역산으로 수량 제시
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL — 복수 매도 후보 처리 (stage_0)
|
||||
|
||||
SELL/TRIM/EXIT 후보가 2개 이상이거나 현금 < 목표인 경우:
|
||||
1. **`?view=sell_priority` 결과를 먼저 출력** (GAS runSellPriority() 엔드포인트)
|
||||
2. `sell_priority_decision_table` 출력 후 tier 1→2→3→4→9 순서로 후보 선정
|
||||
3. sell_priority_decision_table 없이 특정 종목을 1차 매도 대상으로 확정 금지
|
||||
|
||||
**코어 주도주 보호:** SK하이닉스·삼성전자는 tier=9 (마지막 순위). 상승추세 중 1차 대상 선정 금지.
|
||||
|
||||
---
|
||||
|
||||
## 분석 절차
|
||||
|
||||
1. Read `RetirementAssetPortfolio.yaml` first.
|
||||
2. Apply source-of-truth files in manifest order.
|
||||
3. Run the decision process through `spec/09_decision_flow.yaml`.
|
||||
4. Apply `spec/risk/risk_control.yaml`, `spec/risk/portfolio_exposure.yaml`, and `spec/08_scoring_rules.yaml` hard filters before scoring.
|
||||
5. Classify market regime using `spec/11_market_regime.yaml`.
|
||||
6. Apply `spec/10_portfolio_rules.yaml` before any position sizing.
|
||||
7. Use `schemas/output_schema.json` for JSON output and `RetirementAssetPortfolioReportTemplate.yaml` for human-readable reports.
|
||||
8. Every decision must cite rule paths in `rules_used`.
|
||||
9. If data is missing, do not infer numbers. Use `INSUFFICIENT_DATA` and fill `prohibited_calculations`.
|
||||
10. Render in two phases: first complete a schema-valid structured payload, then map only those values into the human-readable tables.
|
||||
11. For human-readable reports, output the required tables in the sequence from `spec/07_output_schema.yaml:recommendation_grade.display_policy.output_sequence`.
|
||||
12. Do not create free-form section headers or paragraph summaries that replace required tables. Put rationale inside table columns such as `근거`, `매도사유`, or `다음확인사항`.
|
||||
13. Use only schema enum values for `order_type`, `mode`, and `validation_status`; map them to Korean display labels only after validation.
|
||||
14. When the user asks for learning-oriented context, add `market_context_learning_note` after the order validation and HTS tables. Use it only for explanation; do not create new prices, quantities, cash figures, or decisions in that section.
|
||||
15. Do not use free-form order terms such as `부분감액`, `1차 감액`, `부분정리`, `전량`, or `전량매도` in order/action/mode columns. Use canonical enum values and integer quantities instead.
|
||||
16. When two or more SELL/TRIM/ROTATE candidates exist, output `sell_priority_decision_table` before HTS-ready order tables and sort by `portfolio_exposure_framework.sell_priority_engine`.
|
||||
17. Always populate `decision_trace` and render `decision_trace_table` before any final action table. Do not use a rule or rationale in the final answer unless it appears in `decision_trace` or `rules_used`.
|
||||
|
||||
## 필수 출력 요소
|
||||
|
||||
- `BUY`, `HOLD`, `SELL`, `TRIM`, `ROTATE`, `AVOID`, `WATCH`, or `INSUFFICIENT_DATA`
|
||||
- grade `A/B/C/D/NONE`
|
||||
- top-level `analysis_scope`, `scores`, `position_sizing`, `triggered_rules`, `missing_data`, `invalidation_conditions`, `evidence`, and `rule_ids_used`
|
||||
- HTS order fields only when validation passes
|
||||
- `capture_read_ledger` and the 4-stage order validation result before any HTS-ready order table
|
||||
- `decision_trace` / `decision_trace_table` for reproducible rule application
|
||||
- `sell_priority_decision_table` when multiple sell/trim/rotate candidates compete
|
||||
- optional `market_context_learning_note` when educational context is requested
|
||||
- final `engine_feedback_loop_report` using `proposal_evaluation_history`; compare prior proposals with next-day results and include improvement proposals without changing current orders.
|
||||
- next source to check when validation fails
|
||||
|
||||
## 보고서 출력 구조: Section A / B / C [2026-05-19_LLM_SERVICE_LAYER_V1 S2]
|
||||
|
||||
모든 분석 보고서는 아래 3개 섹션으로 **순서대로** 구성한다. 섹션 순서 변경 금지.
|
||||
|
||||
---
|
||||
|
||||
### [Section A] 하드-하네스 원장 (The Ledger)
|
||||
|
||||
**오너: 하네스 + LLM as Reporter/Clerk**
|
||||
|
||||
포함 내용 (순서 고정):
|
||||
1. `routing_serving_trace` (라우팅·번들·프롬프트·JSON 검증 상태) ← QEH 이전 필수
|
||||
2. `QEH_AUDIT_BLOCK` (공식 검산 표)
|
||||
3. `PROACTIVE_RADAR_BLOCK` (선제 매도 레이더 결과) ← 보유 포지션 분석 시 필수
|
||||
4. `capture_read_ledger` (계좌 판독 원장)
|
||||
5. `data_completeness_matrix`
|
||||
6. `backdata_feature_bank_table` (GAS 자동 수집 백데이터 우선)
|
||||
7. `benchmark_relative_harness_table`
|
||||
8. `alpha_lead_table`
|
||||
9. `anti_distribution_table`
|
||||
10. `profit_preservation_table`
|
||||
11. `smart_cash_raise_table`
|
||||
12. `execution_quality_table`
|
||||
13. `order_quantity_4stage_gate`
|
||||
14. `decision_trace_table`
|
||||
15. `sell_priority_decision_table` (해당 시)
|
||||
16. HTS 주문표 (지정가·수량·tick_status 포함) — `validation_status=PASS` 행만 기재
|
||||
17. WATCH 감시 원장 (`reference_price_ledger`) — 주문 아님, `validation_status != PASS` 행 전용
|
||||
18. **`comprehensive_proposal_table`** (PROPOSAL_TABLE, HS010-B) — **항상 필수 출력. PENDING_EXPORT·BLOCKED 무관하게 생략 금지.** `comprehensive_proposal_json` 기반으로 전체 보유 종목 판단 자료를 표로 출력한다.
|
||||
19. `core_satellite_timing_gate_table` — 후보등급과 실행추천상태를 분리하고 T+1 강제매도 위험·매도충돌을 검산.
|
||||
20. **`satellite_candidate_table`** — **항상 필수 출력.** `satellite_candidate_json` 기반 위성 후보 스크리닝 결과. 후보 없어도 표를 출력한다 (ALL_EXCLUDED 명시).
|
||||
21. `engine_feedback_loop_report` — 전일 제안값과 다음 거래일 결과 비교, 원인분석, 개선 제안. 보고서 마지막에만 출력.
|
||||
|
||||
**보고서 완성도 게이트:**
|
||||
- 위 필수 표 중 하나라도 누락되면 `BLOCKED_REPORT`로 처리하고 최종 매수·매도 결론을 산문으로 확정하지 않는다.
|
||||
- `execution_quality_table` 없이 `validation_status=PASS` 주문표 출력 금지.
|
||||
- `smart_cash_raise_table` 없이 현금확보 매도수량·순서 출력 금지.
|
||||
- **`comprehensive_proposal_table`와 `satellite_candidate_table`은 PENDING_EXPORT, BLOCKED, DATA_MISSING 어떤 상태에서도 생략 불가.**
|
||||
|
||||
**[HS010] HTS 주문표 vs WATCH 감시 원장 분리 규칙 (I4):**
|
||||
|
||||
| 구분 | HTS 주문표 (실행 가능) | WATCH 감시 원장 (참고 전용) |
|
||||
|---|---|---|
|
||||
| 적용 행 | `validation_status=PASS`인 행만 | `validation_status=BLOCKED/WATCH/PENDING` 행 |
|
||||
| 허용 컬럼 | 종목, 계좌, 지정가, 수량, 주문유형, 손절가 | `ticker/name`, `reference_stop_price`, `reference_tp_state`, `hts_allowed=false`, `reason_code` |
|
||||
| 가격/수량 기재 | HTS 입력 가능한 확정값만 | **절대 금지** — `estimated_sell_krw` 등 참고 수치도 이 표에 기재 금지 |
|
||||
| 표 제목 | "HTS 입력 가능 주문표" | "WATCH 감시 원장 — 주문 아님, HTS 입력 금지" |
|
||||
|
||||
**HS010 절대 금지 컬럼명** (WATCH 감시 원장에 사용 시 INVALID_COLUMN 처리):
|
||||
`지정가`, `손절가`, `익절가`, `매도가`, `주문가`, `주문수량`, `손절수량`, `익절수량`, `매도수량`, `주문금액`
|
||||
|
||||
**HS010 허용 컬럼명** (WATCH 감시 원장 전용):
|
||||
`reference_stop_price` (prices_json 복사값), `reference_tp_state` (PENDING/INVALID/ALREADY_TRIGGERED), `hts_allowed` (항상 false), `reason_code` (NO_EXECUTION:WATCH 등)
|
||||
|
||||
**[HS010-B] 종합 판단 제안표 (PROPOSAL_TABLE) — 항상 필수:**
|
||||
|
||||
`comprehensive_proposal_json`을 아래 형식으로 항상 출력한다. `json_validation_status=PENDING_EXPORT`이거나 `order_blueprint_json`이 전부 BLOCKED여도 이 표는 반드시 출력한다. 판단은 사용자가 하므로, 판단에 필요한 모든 수치를 제공하는 것이 시스템의 역할이다.
|
||||
|
||||
| 종목 | composite_verdict | 참고 손절가 | 참고 익절1 (TP1 상태) | 참고 익절2 (TP2 상태) | 즉시 매도 수량 | 단계 매도 수량 | 예상 회수 금액 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| (ticker/name) | (composite_verdict) | (reference_stop_price) | (reference_tp1_price / tp1_state) | (reference_tp2_price / tp2_state) | (proposed_immediate_qty) | (proposed_staged_qty) | (expected_cash_krw) |
|
||||
|
||||
- 표 제목: **"종합 판단 제안표 (PROPOSAL) — 확정 주문 아님, 사용자 최종 판단"**
|
||||
- TP 상태 표기: `PENDING`=미달성, `TP1_ALREADY_TRIGGERED`=1차 이미 완료, `TP2_ALREADY_TRIGGERED`=2차 이미 완료, null=해당 없음
|
||||
- 참고 가격이 null이면 TP 상태 그대로 표기하되, "–"로 공란 처리 금지 (상태 명시)
|
||||
- LLM이 이 표의 수치를 임의 수정하거나 새 가격을 추가하는 것은 금지. `comprehensive_proposal_json` 값 그대로 표기.
|
||||
|
||||
**[HS010-C] 위성 후보 스크리닝 표 (SATELLITE_CANDIDATE_TABLE) — 항상 필수:**
|
||||
|
||||
`satellite_candidate_json`과 `satellite_candidate_summary`를 아래 형식으로 항상 출력한다. 후보가 0개여도 표를 출력하고 "스크리닝 결과 신규 후보 없음"을 명시한다.
|
||||
|
||||
| 종목 | 섹터 | 등급 | 현재가 | MA20 이격 | 허용 액션 | 스크리닝 상태 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| (ticker/name) | (sector) | (grade) | (close) | (ma20_disparity_pct%) | (allowed_action) | (screen_status) |
|
||||
|
||||
- 표 제목: **"위성 후보 스크리닝 현황 (SATELLITE_CANDIDATE_SCREEN_V1)"**
|
||||
- 등급 A·B는 WATCH_CANDIDATE, C·D·F는 BELOW_THRESHOLD로 구분
|
||||
- `satellite_candidate_summary.watch_candidates`가 0이면 "현재 추가 적합 후보 없음" 명시
|
||||
- LLM이 universe 외 종목을 임의로 추가하거나 등급을 변경하는 것 금지
|
||||
|
||||
**Section A 절대 금지:**
|
||||
- 형용사·서사·"~할 수 있습니다" 등 완화 표현 삽입 → [HARNESS_LOCKED] 태그 위반
|
||||
- 수치·등급·최종행동 결론 임의 변경
|
||||
- WATCH 감시 원장을 HTS 주문표와 같은 표로 합산 또는 같은 컬럼명 사용 → HS010 위반
|
||||
- **`comprehensive_proposal_table`·`satellite_candidate_table` 생략 → `INVALID_MISSING_PROPOSAL` 처리**
|
||||
|
||||
---
|
||||
|
||||
### [Section B] 전문 애널리스트 브리핑 (The Briefing)
|
||||
|
||||
**오너: LLM as Expert Analyst — Section A 완성 후에만 작성**
|
||||
|
||||
허가된 역할:
|
||||
- **expert_commentary**: 하네스 결정의 거시경제·시장 맥락 해설
|
||||
- 예) "현금 8.95% → CASH_RAISE_REQUIRED — 달러 강세와 VIX 반등이 방어현금 10%의 근거"
|
||||
- **reasoning_review**: 매도 순위·등급·TRIM 결정의 수급·실적 논리 근거 풀이
|
||||
- 예) "기아 SELL_PRIORITY 1순위 — flow_credit 0.30, RW 4, 섹터 모멘텀 둔화"
|
||||
- **economic_insights**: VIX·환율·금리차가 MRS 점수에 영향을 준 경로를 교육적으로 설명
|
||||
|
||||
**Section B 절대 금지:**
|
||||
- Section A의 결론 번복 또는 완화 ("그래도 매수 고려 가능", "상황에 따라 유연하게")
|
||||
- Section B 내에서 새 가격·수량·등급 숫자 독자 생성
|
||||
|
||||
---
|
||||
|
||||
### [Section C] 용어 사전 및 학습 가이드 (The Glossary)
|
||||
|
||||
**오너: LLM as Educator — 보고서 내 주요 지표 3개 이상 시 자동 생성**
|
||||
|
||||
필수 수록 항목 (등장 시):
|
||||
|
||||
| 용어 | 정의 | 임계값 의미 |
|
||||
|------|------|-----------|
|
||||
| MRS (Market Risk Score) | 시장위험 종합점수 0~10 | 6↑ → 목표현금 14%+ |
|
||||
| flow_credit | 가격·거래량·5D 수급 품질 0~1 | 0.40 미만 → Kelly 반감 |
|
||||
| ATR20 | 최근 20거래일 평균 일일변동폭 | 손절가 계산 기준 |
|
||||
| Total Heat | 손절 기준 총 위험노출 % | 10%↑ → 신규매수 전면 차단 |
|
||||
| CSR001 | 현금 부족 시 TRIM 자동 배정 메커니즘 | 부족액/현재가 = trim_qty |
|
||||
|
||||
| DIVERGENCE_SCORE | 가격↑ 수급↓ 다이버전스 0~1 | ≥0.70 → ALERT |
|
||||
| OVERHANG_PRESSURE | 외국인 오버행 압박 0~1 | 1.00 → ALERT |
|
||||
| ROTATION_RADAR | 섹터 로테이션 초기 신호 | ALERT → 선제 TRIM |
|
||||
| flow_accel_ratio | 외인 매수 에너지 가속비 (W4) | <0.50 → FLOW_DECEL_WARNING |
|
||||
| deviation_ratio | 현재가/MA20 이격도 비율 (X1) | ≥1.15 → BUY_HARD_BLOCK |
|
||||
| rs_ratio | 종목 5D 수익/KOSPI 5D 수익 (X3) | <0.80 → RS_LAGGARD (매도 우선) |
|
||||
| CRITICAL_ALERT | 2개 이상 레이더 동시 발화 | 전면 포트폴리오 재검토 |
|
||||
|
||||
용어 정의 기준: `spec/12_field_dictionary.yaml` 준수. 임의 수치·예시 생성 금지.
|
||||
|
||||
---
|
||||
|
||||
## 주문표 수량 기재 기준 요약
|
||||
|
||||
| Account_Parse_Status | 매도수량 기재 | 매수수량 기재 |
|
||||
|----------------------|--------------|--------------|
|
||||
| CAPTURE_READ_OK | 정수 가능 (보유수량 ≤ 확인수량) | ATR20 확인 시 정수 가능 |
|
||||
| MANUAL_ENTRY | 캡처확인후기재 | ATR20 확인 시 정수 가능 |
|
||||
| STALE_MANUAL | 캡처확인후기재 | ATR20 확인 시 정수 가능 |
|
||||
| "" (미제공) | 캡처확인후기재 | ATR20 확인 시 정수 가능 |
|
||||
|
||||
## 주문표 tick_status 컬럼 필수 기재 [2026-05-19_HARNESS_AUDIT_V1 H3]
|
||||
|
||||
모든 HTS 주문표의 지정가(매수·손절·익절·trailing_stop) 옆에 `tick_status` 컬럼을 반드시 출력한다.
|
||||
|
||||
| 상태 | 표기 형식 | 의미 |
|
||||
|------|----------|------|
|
||||
| 정규화 완료 | `[TICK_OK: 144,500원]` | TICK_NORMALIZER_V1 통과. HTS 입력 가능 |
|
||||
| 정규화 필요 | `[TICK_INVALID: 144,568원 → 144,500원 재산출]` | 원래 가격이 호가 단위 불일치 → 재산출 적용 |
|
||||
| 가격 미산출 | `[TICK_SKIP: 가격 없음]` | 수량/가격 산출 불가 상황 |
|
||||
|
||||
- `tick_status` 없는 지정가 → `INVALID_TICK_UNTAGGED`, `검산_통과여부 = FAIL`
|
||||
- TICK_NORMALIZER_V1 공식 적용 후 `[TICK_OK]` 태그 부착이 확인된 가격만 HTS 입력 허용
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL — PROACTIVE_RADAR_BLOCK (선제 매도 레이더) [2026-05-19_PROACTIVE_RADAR_V1 W1~W3]
|
||||
|
||||
**보유 포지션이 있는 모든 분석에서 QEH_AUDIT_BLOCK 직후 PROACTIVE_RADAR_BLOCK을 출력한다.**
|
||||
이 블록 없이 매도 판단을 생략하면 `RADAR_MISSING_BLOCK` 처리한다.
|
||||
|
||||
### PROACTIVE_RADAR_BLOCK 표 형식 (필수)
|
||||
|
||||
**[우선순위 1] harness_context에 `alpha_shield_json` 존재 시 → GAS 확정값 직접 읽기 (LLM 재계산 금지)**
|
||||
|
||||
각 보유 종목의 `alpha_shield_json[ticker]` 에서 `w1_status`, `w2_status`, `w3_status`, `w4_status`,
|
||||
`deviation_ratio`, `rs_ratio`, `overhang_pressure`, `flow_accel_ratio`, `critical_alert` 를 읽어 아래 표에 기재.
|
||||
|
||||
| 레이더 | 공식_ID | harness 필드 | 점수/상태 | 발화 결과 |
|
||||
|--------|---------|-------------|----------|---------|
|
||||
| W1 가격-수급 다이버전스 | DIVERGENCE_SCORE_V1 | `w1_status`, `volume_ratio` | volume_ratio | CLEAR / DIVERGENCE_ALERT |
|
||||
| W2 외국인 오버행 압박 | OVERHANG_PRESSURE_V1 | `w2_status`, `overhang_pressure` | overhang_pressure | CLEAR / OVERHANG_WARNING |
|
||||
| W3 섹터 로테이션 신호 | SECTOR_ROTATION_RADAR_V1 | `w3_status`, `sector_rank` | rank 변화 | CLEAR / ROTATION_WARNING |
|
||||
| W4 수급 에너지 소진 | FLOW_ACCELERATION_V1 | `w4_status`, `flow_accel_ratio` | flow_accel_ratio | CLEAR / FLOW_DECEL_WARNING |
|
||||
| X1 이격 BUY 차단 | MEAN_REVERSION_GATE_V1 | `mrg_gate`, `deviation_ratio` | deviation_ratio | PASS / BUY_HARD_BLOCK |
|
||||
| X3 RS 상대강도 | RS_RATIO_V1 | `rs_status`, `rs_ratio` | rs_ratio | RS_OK / RS_LAGGARD |
|
||||
| **종합** | — | `critical_alert` | radar_fires | **PASS / CLEAR / CRITICAL_ALERT** |
|
||||
|
||||
**[우선순위 2] `alpha_shield_json` 없는 경우 → DATA_MISSING 처리 (HS011)**
|
||||
|
||||
`alpha_shield_json` 이 없으면 `"DATA_MISSING — alpha_shield_json 미산출. GAS 하네스 재실행 필요"` 만 출력한다.
|
||||
LLM이 data_feed 값을 직접 읽어 Alpha Shield 점수를 계산하는 것은 HS011 위반으로 금지된다.
|
||||
|
||||
| 레이더 | 공식_ID | 입력값 요약 | 점수/상태 | 발화 결과 |
|
||||
|--------|---------|-----------|----------|---------|
|
||||
| W1 가격-수급 다이버전스 | DIVERGENCE_SCORE_V1 | MA20 위치, frg_5d_sh, inst_5d_sh, flow_credit | X.XX (0~1) | PASS / CAUTION / ALERT |
|
||||
| W2 외국인 오버행 압박 | OVERHANG_PRESSURE_V1 | frg_5d/20d_sh, volume vs avg_volume_5d | X.XX (0~1) | PASS / CAUTION / ALERT |
|
||||
| W3 섹터 로테이션 신호 | SECTOR_ROTATION_RADAR_V1 | SmartMoney_5D_Norm_Score, Rank 변화 | FLAG | PASS / CAUTION / ALERT |
|
||||
| W4 수급 에너지 소진 | FLOW_ACCELERATION_V1 | frg_5d_sh, frg_20d_sh, MA20 위치 | flow_accel_ratio (0~1+) | PASS / FLOW_DECEL_WARNING |
|
||||
| **종합** | — | — | — | **PASS / CAUTION / ALERT / CRITICAL_ALERT** |
|
||||
|
||||
### 레이더 발화 행동 규칙
|
||||
|
||||
| 종합 상태 | 의무 행동 |
|
||||
|----------|---------|
|
||||
| PASS | 정상 분석 계속. 레이더 결과 표만 출력. |
|
||||
| CAUTION | Section B에 `reasoning_review`로 원인 해설 필수. 보유 유지 가능하나 모니터링 강화. |
|
||||
| ALERT | `sell_priority_engine` 즉시 실행. Section A에 TRIM 후보 표 출력. |
|
||||
| CRITICAL_ALERT | 코어 포지션 포함 **전면 포트폴리오 재검토** 강제. "가격이 멀쩡해도 매도 예술 구간" 경고 출력. |
|
||||
|
||||
### PROACTIVE_RADAR_BLOCK 절대 금지
|
||||
|
||||
- 레이더 ALERT 이상 발화 시 sell_priority_engine 미실행 → **RADAR_ACTION_SKIPPED**
|
||||
- LLM이 레이더 결과를 서사로 완화 ("아직 괜찮습니다", "좀 더 지켜볼 만합니다") → **RADAR_OVERRIDE_FORBIDDEN**
|
||||
- 데이터 부족 시 레이더 결과를 PASS로 처리 → **DATA_MISSING은 RADAR_MISSING으로 표기**
|
||||
- 코스피 지수 상승을 이유로 ALERT 레이더 무효화 → **INDEX_OVERRIDE_FORBIDDEN**
|
||||
|
||||
### 데이터 누락 처리
|
||||
|
||||
| 누락 데이터 | 처리 |
|
||||
|-----------|------|
|
||||
| frg_5d_sh | W1/W2 DATA_MISSING. "외국인 수급 데이터 필요 — 수동 점검 권고" 출력 |
|
||||
| avg_volume_5d | W2 volume_weakness=false (보수적 처리) |
|
||||
| sector_flow | W3 DATA_MISSING. "sector_flow 탭 점검 권고" 출력 |
|
||||
| 전체 수급 데이터 없음 | RADAR_MISSING — soft-block: "수급 데이터 없이 보유 포지션 판단 제한" |
|
||||
@@ -0,0 +1,185 @@
|
||||
# HTS 캡처 파싱 프롬프트
|
||||
|
||||
HTS 캡처 이미지가 제공되면 이 프롬프트를 **분석보다 먼저** 실행한다.
|
||||
출력 순서: ① 화면판별 → ② 검증 → ③ 충돌감지·캡처우선 선언 → ④ TSV붙여넣기 → ⑤ account_snapshot 상태갱신 → ⑥ 현금요약 → ⑦ 인라인재산출 → ⑧ 다음단계
|
||||
|
||||
---
|
||||
|
||||
## STEP 1 — 화면 종류 판별
|
||||
|
||||
| 화면 | 판별 기준 | 사용 가능 여부 |
|
||||
|------|---------|-------------|
|
||||
| **보유종목 화면** | 종목명·수량·평단·평가손익이 있음 | ✅ 잔고 원장으로 사용 |
|
||||
| **현금/예수금 화면** | 예수금·D+2·주문가능금액이 있음 | ✅ 현금 원장으로 사용 |
|
||||
| **자동투자/적립식 화면** | 월 납입금액·약정일이 있음 | ❌ 잔고 원장 사용 금지 (AS006) |
|
||||
| **미체결 화면** | 주문종목·미체결수량이 있음 | ✅ open_order_amount 참조용 |
|
||||
|
||||
화면 판별 결과를 첫 줄에 명시한다. 예: `[보유종목 화면 확인] 일반계좌`
|
||||
|
||||
---
|
||||
|
||||
## STEP 2 — 검증 (출력 전 필수)
|
||||
|
||||
| 규칙 ID | 확인 내용 | 실패 시 |
|
||||
|---------|---------|--------|
|
||||
| AS001 | 숫자 앞의 라벨 확인 (수량: X주, 평단: X원) | parse_status = CAPTURE_READ_FAILED |
|
||||
| AS002 | holding_quantity 정수 확인 (소수 불가) | CAPTURE_READ_FAILED |
|
||||
| AS003 | market_value ≈ holding_quantity × current_price (오차 1% 이내) | DATA_CONFLICT 표시 |
|
||||
| AS004 | 현금 필드 0 이상 | CAPTURE_READ_FAILED |
|
||||
| AS006 | 자동투자 화면 여부 확인 | CAPTURE_PROVIDED_BUT_NOT_HOLDINGS |
|
||||
|
||||
검증 결과를 한 줄로 출력: `[검증] AS001 OK | AS002 OK | AS003 OK (오차 0.3%) | parse_status = CAPTURE_READ_OK`
|
||||
|
||||
---
|
||||
|
||||
## STEP 2b — 캡처 vs JSON/account_snapshot 충돌 감지 (캡처 우선)
|
||||
|
||||
**출력 조건**: `GatherTradingData.json` 또는 account_snapshot 데이터가 이번 대화에 업로드되어 있을 때
|
||||
|
||||
기존 시장 데이터(JSON `data.data_feed`의 Account_Holding_Qty·Account_Avg_Cost)와 캡처값을 종목별로 비교한다.
|
||||
|
||||
```
|
||||
[충돌 감지 결과]
|
||||
ticker | 항목 | JSON/GAS값 | 캡처(실시간) | 차이 | 우선권
|
||||
000660 | holding_quantity | 80주 | 100주 | +20주 | ★캡처우선
|
||||
000660 | average_cost | 175,000원 | 170,000원 | -5,000원 | ★캡처우선
|
||||
012450 | holding_quantity | 20주 | 20주 | 없음 | 일치
|
||||
```
|
||||
|
||||
**캡처 우선 규칙**:
|
||||
- 캡처값 ≠ GAS값 → **캡처가 정답**. 이후 모든 분석은 캡처값 기준으로 진행.
|
||||
- GAS값은 account_snapshot 붙여넣기 전 상태의 오래된 값임.
|
||||
- JSON에 Account_Parse_Status=MANUAL_ENTRY/STALE_MANUAL/""인 종목도 캡처가 있으면 **캡처우선**.
|
||||
- 충돌 없으면: `[충돌 없음] 모든 종목 일치`
|
||||
|
||||
**주의**: 충돌 종목에 대해 이후 분석에서 GAS의 Profit_Pct·Weight_Pct·Sell_Qty 등은 재산출 대상.
|
||||
재산출 방법은 STEP 7 참조.
|
||||
|
||||
---
|
||||
|
||||
## STEP 3 — account_snapshot 탭 붙여넣기 표
|
||||
|
||||
**출력 조건**: 보유종목 또는 현금 화면이 CAPTURE_READ_OK일 때
|
||||
|
||||
**붙여넣기 방법**: Google Sheets → `account_snapshot` 탭 → **A3 셀 선택** → Ctrl+V
|
||||
|
||||
TSV(탭구분) 형식으로 출력한다. 각 행은 account_snapshot 탭의 1개 데이터 행이다.
|
||||
현금 전용 행은 ticker·name·holding_quantity 이하 종목 컬럼을 빈칸으로 두고 현금 컬럼만 채운다.
|
||||
|
||||
```
|
||||
[account_snapshot 탭 — A3 셀에 붙여넣기]
|
||||
captured_at account account_type ticker name holding_quantity available_quantity average_cost total_cost current_price market_value profit_loss return_pct immediate_cash settlement_cash_d2 available_cash open_order_amount monthly_contribution_limit monthly_contribution_used parse_status user_confirmed stop_price highest_price_since_entry entry_date entry_stage position_type last_updated
|
||||
```
|
||||
|
||||
**컬럼 순서 고정 — 27컬럼** (변경 금지):
|
||||
`captured_at | account | account_type | ticker | name | holding_quantity | available_quantity | average_cost | total_cost | current_price | market_value | profit_loss | return_pct | immediate_cash | settlement_cash_d2 | available_cash | open_order_amount | monthly_contribution_limit | monthly_contribution_used | parse_status | user_confirmed | stop_price | highest_price_since_entry | entry_date | entry_stage | position_type | last_updated`
|
||||
|
||||
**작성 규칙**:
|
||||
- `captured_at`: 첫 번째 컬럼. `YYYY-MM-DD HH:MM` 형식. 화면 시각 기준. 없으면 당일 날짜.
|
||||
- `ticker`: 6자리 문자열 (예: `000660`). 이름만 보이면 종목코드로 변환. 불확실하면 빈칸.
|
||||
- `holding_quantity`: 정수만. 소수점 불가.
|
||||
- `available_quantity`: 매도가능수량 (HTS 화면에 표시되는 경우). 없으면 빈칸.
|
||||
- `average_cost` / `current_price` / `market_value` / `total_cost`: 원 단위 정수 (콤마 없이).
|
||||
- `total_cost`: 매입금액 = holding_quantity × average_cost. HTS에서 직접 읽으면 그 값 사용.
|
||||
- `profit_loss`: 평가손익 (원). HTS 화면 값 직접 사용. 없으면 빈칸.
|
||||
- `return_pct`: 수익률 %. 예: `7.5` (% 기호 제외). 없으면 빈칸.
|
||||
- 현금 필드: 보유종목 화면에서 현금이 보이지 않으면 빈칸 (0으로 채우지 않음).
|
||||
- `parse_status`: 검증 통과 시 `CAPTURE_READ_OK`, 실패 시 `CAPTURE_READ_FAILED`.
|
||||
- `user_confirmed`: 항상 `Y` (붙여넣기 행위 자체가 사용자 확인임).
|
||||
- 선택 포지션 상태 6컬럼(`stop_price`~`last_updated`)은 캡처에서 확인되지 않으면 빈칸. 기존 값을 보존해야 하면 STEP 4에서 해당 셀만 수정.
|
||||
|
||||
**출력 예시** (27컬럼 — 탭으로 구분):
|
||||
```
|
||||
[account_snapshot 탭 — A3 셀에 붙여넣기]
|
||||
2026-05-18 09:30 일반계좌 일반계좌 000660 SK하이닉스 100 100 170000 17000000 181900 18190000 1190000 7.0 3500000 4200000 3500000 0 CAPTURE_READ_OK Y
|
||||
2026-05-18 09:30 일반계좌 일반계좌 012450 한화에어로스페이스 20 20 980000 19600000 1216000 24320000 4720000 24.1 0 CAPTURE_READ_OK Y
|
||||
2026-05-18 09:30 일반계좌 일반계좌 현금 3500000 4200000 3500000 0 CAPTURE_READ_OK Y
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STEP 4 — account_snapshot 선택 포지션 상태 갱신 표
|
||||
|
||||
**출력 조건**: stop_price, highest_price_since_entry, entry_stage, position_type, last_updated 갱신이 필요한 경우
|
||||
|
||||
**붙여넣기 방법**: 아래 표를 참고해 `account_snapshot` 탭에서 ticker 행을 찾아 해당 선택 열만 수정.
|
||||
보유수량·평단은 STEP 3의 account_snapshot TSV가 담당한다. `positions` 탭은 사용하지 않는다.
|
||||
|
||||
```
|
||||
[account_snapshot 탭 — 해당 ticker 행의 아래 선택 열을 수동 수정]
|
||||
ticker stop_price highest_price_since_entry entry_stage position_type last_updated
|
||||
```
|
||||
|
||||
**작성 규칙**:
|
||||
- `stop_price`가 비어 있으면 GAS가 ATR 기반으로 추정한다. 수동 입력을 강제하지 않는다.
|
||||
- `last_updated` = 오늘 날짜 `YYYY-MM-DD`.
|
||||
- `position_type`은 `core` 또는 `satellite`. 비어 있으면 GAS가 `satellite`로 간주한다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 5 — 현금 요약 (1줄)
|
||||
|
||||
```
|
||||
[현금 요약] 즉시현금: X원 | D+2현금: X원 | 주문가능: X원 | 미체결: X원
|
||||
→ settings 탭 settlement_cash_d2_krw에 X원 입력 필요
|
||||
```
|
||||
|
||||
D+2현금을 settings 탭에 수동 입력해야 GAS가 매수 가능 금액 계산에 사용한다.
|
||||
|
||||
---
|
||||
|
||||
## STEP 6 — 다음 단계 안내 (항상 출력)
|
||||
|
||||
```
|
||||
[다음 단계]
|
||||
1. account_snapshot 탭 A3에 위 표 붙여넣기
|
||||
2. 필요한 경우 account_snapshot 탭에서 ticker별 stop_price·highest_price_since_entry·last_updated 선택 열만 수정
|
||||
3. settings 탭 settlement_cash_d2_krw = [D+2현금 값] 입력
|
||||
4. GAS runDataFeed() 재실행 → xlsx 원본 갱신 확인
|
||||
5. `npm run convert-data-json` 실행 후 갱신된 `GatherTradingData.json` 업로드 후 분석 요청
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## STEP 7 — 충돌 종목 인라인 재산출 (캡처 우선 적용)
|
||||
|
||||
**출력 조건**: STEP 2b에서 충돌 종목이 1개 이상 발견된 경우
|
||||
|
||||
GAS 재실행 전이라도 캡처값으로 핵심 지표를 직접 재산출해서 분석에 사용한다.
|
||||
JSON/GAS의 해당 종목 계좌 값은 **무시**하고 아래 재산출값으로 교체한다.
|
||||
|
||||
### 재산출 항목
|
||||
|
||||
| 항목 | 재산출 공식 | 필요 입력 |
|
||||
|------|-----------|---------|
|
||||
| `Profit_Pct` | `(현재가 - 캡처평단) / 캡처평단 × 100` | 캡처평단, JSON Close |
|
||||
| `Unrealized_PnL` | `(현재가 - 캡처평단) × 캡처수량` | 캡처수량·평단, JSON Close |
|
||||
| `Weight_Pct` | `(현재가 × 캡처수량) / 총자산 × 100` | 캡처수량, JSON Close, 총자산 |
|
||||
| `Sell_Qty` | `캡처수량 × Sell_Ratio_Pct / 100` (반올림) | 캡처수량, GAS Sell_Ratio_Pct |
|
||||
| `매도가능수량` | `캡처 available_quantity` 있으면 상한 적용 | 캡처 available_quantity |
|
||||
|
||||
### 출력 형식
|
||||
|
||||
```
|
||||
[캡처 우선 재산출] — GAS 재실행 전 임시값
|
||||
ticker | 항목 | GAS값(무효) | 캡처재산출값 | 비고
|
||||
000660 | Profit_Pct | +4.2% | +6.9% | 평단 변경(175000→170000)
|
||||
000660 | Unrealized_PnL| 3,360,000원 | 1,190,000원 | 수량 변경(80→100)
|
||||
000660 | Weight_Pct | 7.8% | 9.7% | 수량 변경
|
||||
000660 | Sell_Qty | (미산출) | 25주 | TRIM_25(25%) × 100주
|
||||
```
|
||||
|
||||
**재산출 불가 항목** (GAS 의존): SS001_Grade, RW_Partial, Flow_Credit, Final_Action 등 시장데이터 기반 점수.
|
||||
이 항목들은 JSON 시장데이터 값을 그대로 사용하되, 수량·손익 계산만 캡처값으로 교체한다.
|
||||
|
||||
---
|
||||
|
||||
## 오류 처리
|
||||
|
||||
| 상황 | 출력 |
|
||||
|------|------|
|
||||
| 이미지 흐림·잘림으로 숫자 판독 불가 | 해당 필드 빈칸 + `parse_status=CAPTURE_READ_FAILED` |
|
||||
| 종목코드 불확실 | ticker 빈칸 + name만 기재 + 경고 메모 |
|
||||
| AS003 오차 > 1% | DATA_CONFLICT 표시 후 계속 기재 (사용자 확인 후 사용) |
|
||||
| 자동투자 화면 | `parse_status=CAPTURE_PROVIDED_BUT_NOT_HOLDINGS` + 보유수량 기재 금지 |
|
||||
| JSON 미업로드 | STEP 2b 생략, STEP 7 생략. 캡처값 단독으로 진행. |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user