Compare commits

...

11 Commits

Author SHA1 Message Date
kjh2064 11e8a3c7f8 docs: final file-count status for this session's cleanup thread
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 23s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 11s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Failing after 10s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 13s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 13s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 2m28s
4,523 -> 1,687 (77% of the 2,200 budget). Records the
test_kis_data_collection_v1.py import fix and the golden-stub removal
reasoning, and explicitly notes that further file-count reduction
isn't warranted right now - the gate has real headroom and continuing
to hunt for deletions has more downside (accidentally cutting
something live) than upside at this point.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 15:02:18 +09:00
kjh2064 65e3769eb2 chore: remove 174 non-executing golden test stubs (M3 parity now done)
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 14s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 24s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Failing after 10s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 11s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 11s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 2m26s
spec/60_quant_engine_wbs.yaml's own QE-M0-06 note explicitly deferred
this: "골든 테스트 174개는 실행되지 않는 placeholder... 삭제는 M3
패리티 대체 후" (the 174 golden tests are non-executing placeholders;
delete once M3 parity replaces them). Confirmed all 5 M3 tasks
(point-in-time reader, factor parity calculator, SS001 composite
score, PipelineOrchestrator honesty, score FE) are status=DONE.

Verified before deleting, not just inferred:
- No .gitea/workflows/*.yml references tests/golden/generated/ - CI
  only runs specific named files under tests/unit/.
- tools/validate_golden_coverage_100.py's coverage ratio is computed
  from spec/formula_golden_cases_v2-v4.yaml entries, not by counting
  these stub files (its own code comment says so).
- Sampled a stub: trivial always-true assertions, one even internally
  broken (`assert outputs` on an empty list) - proof it never actually
  ran.

Re-ran the coverage validator, QE-M0-06's WBS verdict, and full pytest
collection after deletion - all still pass/clean. tests/golden/
itself (README.md, formula_registry_v2_cases.yaml) is untouched, only
the generated/ subdirectory is gone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 15:01:20 +09:00
kjh2064 4dd6098619 fix: broken import in test_kis_data_collection_v1.py (same root cause as F8)
Same class of bug as the earlier tools/run_kis_data_collection_v1.py
fix this session: the module moved to src/quant_engine/deprecated/
but this test still imported the old path. Found by actually running
pytest --collect-only on tests/unit/ (110 tests collect cleanly now,
was failing at 1 error before).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 14:34:01 +09:00
kjh2064 70e49f0556 docs: update handoff notes with orphan cleanup and xlsx findings
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 13s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 22s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Failing after 11s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 11s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 1m51s
Records the two-pass orphan script cleanup (74 files total across two
commits) and the methodology gap that mattered (check output-path
references, not just script-name references). Also records that
GatherTradingData.json (the project's own designated canonical seed,
per docs/GATHERTRADINGDATA_XLSX_DECISION) is available from the
production server even without the untracked .xlsx, but the release
DAG's convert_xlsx node has no skip-if-output-exists logic, and the
remaining 10 blocked WBS verdicts need a running QuantEngine.Web +
Playwright evidence run against real DB price history, not just the
seed JSON - not attempted this session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 14:29:49 +09:00
kjh2064 73572b6211 chore: remove 40 more orphaned one-off scripts from tools/
Second pass over the 47 lower-confidence orphan candidates held back
from the earlier 34-file cleanup. This time checked two signals, not
one: (1) is the script's own filename referenced anywhere, and (2)
does the script's own Temp/*.json output get read by any other file
(a script can be "orphaned" by name but still load-bearing if
something else consumes what it produces).

7 of the 47 failed check (2) - their outputs (outcome_ledger_v1.json,
pre_distribution_early_warning_v3.json, shadow_ledger_v1.json,
calibration_registry_v1.json, final_execution_decision_v4.json,
live_outcome_ledger_v1.json, final_decision_packet_v2.json) are read
by other tools even though the producing script's name never appears
elsewhere - kept those 7 in place.

The remaining 40 (WBS-ticket-tagged one-offs: wbs81/92/93/95/96_*,
build_p0-p6_*; and misc build_*/validate_* diagnostics with no
external reference by name or by output) were deleted. Re-ran the
full-repo reference search (zero hits) and quick-mode release DAG +
WBS validator afterward - failure set is unchanged (same 10
tasks already known blocked on missing live trading data / a running
web app + Playwright, unrelated to this deletion).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 14:29:08 +09:00
kjh2064 2439d5e24d docs: record the Temp/ evidence-cache incident in the handoff notes
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 13s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 23s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 12s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 11s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 9s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 1m50s
Entropy cleanup earlier today deleted all of Temp/, which turned out
to include WBS verification evidence (Temp/evidence/*/verdict.json),
not just build cache. Documents what broke, what got recovered, and
what remains genuinely blocked on missing trading data - so this
doesn't get rediscovered from scratch next time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 14:12:42 +09:00
kjh2064 977167a0db chore: remove 34 orphaned one-off scripts from tools/
Systematic scan (not manual per-file review): for every *.py under
tools/ and src/quant_engine/, searched the entire tracked repo for
its filename stem outside itself (code, docs, spec, CI workflows,
tests). 81 of 619 files had zero references anywhere. Deleting all
81 in one pass was judged too risky - some could be intentional
manual-run utilities that just don't get named by other files - so
this commit only removes the two highest-confidence tiers, 34 files:

- Superseded DB init/fix/load scripts, most with an explicit "_v2"/
  "_correct"/"_properly" sibling that replaced them (e.g.
  load_from_xlsx.py -> load_from_xlsx_correct.py,
  initialize_database.py -> initialize_database_v2.py). Verified via
  `git log` that both members of each pair were last touched the same
  day (2026-06-23) and never again - abandoned mid-iteration, not
  live tooling.
- Ad-hoc test/debug scripts sitting in tools/ instead of tests/
  (test_api_components.py, test_build_ui_state*.py, diagnose_api_error.py,
  verify_*.py, validate_json_conversion.py) - one-off interactive
  debugging session artifacts.

Re-ran the full-repo reference search after deletion (zero hits) and
the quick-mode release DAG + WBS validator (both still pass) to confirm
nothing references these files. The other 47 orphan candidates (WBS-
ticket-tagged one-offs and misc build_*/validate_* scripts) are left
in place pending further review - some may be intentional manual
utilities the automated reference scan can't distinguish from dead code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 14:12:09 +09:00
kjh2064 fa0da0b159 docs: add session handoff notes for the 2026-07-30 strategic audit
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 12s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 26s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 13s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 11s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 1m56s
Adds docs/SESSION_HANDOFF_2026-07-30.md with what a future session
needs that isn't derivable from git log: open threads (release DAG
unverified end-to-end, engine_history/quantengine schema collision
left as-is, OpenDART/KRX not yet wired into any workflow) and traps
hit this session worth not repeating (archive-before-grepping
tools/spec/tests, DbUp ordinal sort, runtime/ is tracked history not
scratch space, two Pythons on this machine, entropy audit counting
local build noise). Pointed to it from CLAUDE.md's Known Issues list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 13:43:32 +09:00
kjh2064 823a9a64e2 fix: repository entropy audit was measuring build noise, not repo bloat
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 13s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 23s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 13s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 10s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 1m56s
audit_repository_entropy_v2.py (the "과유불급" file-count gate in the
release DAG) was failing: 4,523 files against a 2,200 budget. Traced
it down before touching anything - the audit's _EXCLUDE_DIRS list
predates several directories this repo's own .gitignore already
excludes (Temp/, dist/, outputs/, publish_artifact/, **/bin/,
**/obj/, test-results/), so every local dotnet build/test run
inflates the count with disposable, zero-git-impact build artifacts.
Verified via `git ls-files` that none of the ~2,400 files in those
paths were tracked, versus runtime/, archive_db/, backups/, and
artifacts/ (246 files, all tracked, all genuine audit-trail/history
data) which must NOT be touched.

Cleaned the untracked build/scratch artifacts from disk and added the
missing directories to _EXCLUDE_DIRS so the metric can't be
re-inflated by normal local development again. Gate now passes at
1,934 files with real headroom, without deleting a single tracked
file. Verified full .NET build still succeeds after removing all
bin/obj folders (clean rebuild, 0 errors/warnings).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 13:38:42 +09:00
kjh2064 0a2f0eb4a1 fix: un-archive two docs that CI/release-gate validators depend on
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 15s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 25s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 12s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 11s
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 12s
Workflow Lint & Validation / Lint All Workflow Files (push) Failing after 13s
Workflow Lint & Validation / Validate Secrets Contract (push) Successful in 6s
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 2m5s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Workflow Lint & Validation / Notify Lint Results (push) Failing after 1s
Qualitative Sell Strategy Pipeline / Validate Qualitative Sell Strategy (push) Failing after 7s
Qualitative Sell Strategy Pipeline / Notify Strategy Validation Status (push) Failing after 1s
docs/ROADMAP_WBS.md and docs/ROADMAP_ENTERPRISE_TEMPLATES_WBS.md were
archived in an earlier commit today as "superseded planning docs," but
that judgment only checked CLAUDE.md/README/other docs for references
- not the actual validator scripts, spec YAMLs, and unit tests that
drive the release gate. Both paths turn out to be load-bearing:

- docs/ROADMAP_WBS.md is read directly by
  tools/validate_quant_engine_wbs_v1.py,
  tools/validate_platform_transition_wbs_v1.py,
  tests/unit/test_validate_quant_engine_wbs_v1.py, and referenced by
  spec/60_quant_engine_wbs.yaml and others.
- docs/ROADMAP_ENTERPRISE_TEMPLATES_WBS.md is read by
  tools/validate_enterprise_crud_specification_v1.py, which runs as
  the "Enterprise Contract Parity Test" step in
  .gitea/workflows/ci-frontend.yml.

Moved both back to their original path, replaced the stale "ARCHIVED"
banner with a note explaining why they can't be archived without first
updating the validators, and fixed the 4 doc cross-references that had
been repointed at docs/archive/. Verified all three affected validators
now exit 0 by actually running them locally.

The other 9 docs archived earlier today (5 CI/CD docs + CICD_ROADMAP.md,
EXECUTION_PLAN_PHASE0_CLOSEOUT_AND_PHASE1_KICKOFF.md,
MODERNIZATION_ROADMAP_VISUAL.md,
MODERNIZATION_STRATEGY_ROADMAP_2026-2027.md) were checked against the
same grep sweep (*.py/*.yaml/*.yml/*.js/*.ts/*.cs/*.json, whole repo)
and have zero references outside markdown - those archivals stand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 12:36:20 +09:00
kjh2064 8dabffc08a chore: commit pre-existing untracked work + finish archived-doc notes
Commits work that predated this session and was sitting untracked:
qualitative sell strategy module (src/quant_engine), the t20 outcome
ledger cron workflow, the QuantEngine masterpiece roadmap doc, a WBS
execution yaml, and shared Razor Pages partials (page header, delete
confirm modal) with their view models. Verified these build clean
(0 errors, 0 warnings) before committing.

Also includes the "ARCHIVED" header notes on 6 docs archived in the
previous commit - the git-mv landed there, but the note text itself
was missed from that commit's staged file list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 12:21:32 +09:00
270 changed files with 1133 additions and 12512 deletions
+17
View File
@@ -0,0 +1,17 @@
name: Daily T+20 Outcome Ledger Build
on:
schedule:
- cron: '0 8 * * 1-5' # KST 17:00 (Mon-Fri)
workflow_dispatch:
jobs:
build-t20-ledger:
runs-on: self-hosted
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Build T+20 Outcome Ledger
run: |
python3 tools/build_operational_t20_outcome_ledger_v1.py
+1
View File
@@ -393,6 +393,7 @@ troubleshooting table: [docs/DEV_WORKFLOWS.md](docs/DEV_WORKFLOWS.md)
- **Python-to-.NET transition**: Legacy `ops:data-collect` remains until .NET collection validated in production (est. Q3 2026). - **Python-to-.NET transition**: Legacy `ops:data-collect` remains until .NET collection validated in production (est. Q3 2026).
- **Gitea Actions limitation**: Act runner ↔ Gitea network issues (workaround: manual SSH deployment active). - **Gitea Actions limitation**: Act runner ↔ Gitea network issues (workaround: manual SSH deployment active).
- **도구 버전 관리 (2026-07-30)**: `_v1`/`_v2`... 접미사로 새 버전의 도구를 추가할 때, 이전 버전이 더 이상 쓰이지 않으면 삭제한다 — git 히스토리로 충분하다. 병행 존재 금지(예: `apply_engine_upgrade_v4.py``_v7.py`가 동시에 남아있는 상태). 단, 다른 파일이 옛 버전을 이름으로 참조 중이면(lint 화이트리스트, 문서 등) 그 참조부터 정리한 뒤 삭제한다. - **도구 버전 관리 (2026-07-30)**: `_v1`/`_v2`... 접미사로 새 버전의 도구를 추가할 때, 이전 버전이 더 이상 쓰이지 않으면 삭제한다 — git 히스토리로 충분하다. 병행 존재 금지(예: `apply_engine_upgrade_v4.py``_v7.py`가 동시에 남아있는 상태). 단, 다른 파일이 옛 버전을 이름으로 참조 중이면(lint 화이트리스트, 문서 등) 그 참조부터 정리한 뒤 삭제한다.
- **2026-07-30 전략 감사 세션 인수인계**: 미해결 항목(release DAG 전체 미검증, `engine_history`/`quantengine` 스키마 중복, OpenDART/KRX 미배선)과 재발 방지 트랩(문서 archive 전 `tools/`·`spec/`·`tests/` 전수 검색 필수, DbUp 정렬 버그 수정 완료, `runtime/`은 이력 데이터라 스크래치 출력 금지 등) — [docs/SESSION_HANDOFF_2026-07-30.md](docs/SESSION_HANDOFF_2026-07-30.md) 참고.
### Reliability & Data Quality ### Reliability & Data Quality
+1 -1
View File
@@ -47,7 +47,7 @@
|---|---| |---|---|
| [`AGENTS.md`](../AGENTS.md) | 운영 헌법, Directory Routing 인덱스 | | [`AGENTS.md`](../AGENTS.md) | 운영 헌법, Directory Routing 인덱스 |
| [`GITEA_SECRETS_SETUP.md`](GITEA_SECRETS_SETUP.md) | Gitea 시크릿 설정/검증 가이드 | | [`GITEA_SECRETS_SETUP.md`](GITEA_SECRETS_SETUP.md) | Gitea 시크릿 설정/검증 가이드 |
| [`ROADMAP_WBS.md`](archive/ROADMAP_WBS.md) | `.gs → Python``xlsx → sqlite` WBS (2026-07-30 archive됨, 최신 상태는 CLAUDE.md Migration Status 참고) | | [`ROADMAP_WBS.md`](ROADMAP_WBS.md) | `.gs → Python``xlsx → sqlite` WBS |
| [`docs/GITEA_TOKEN_HOME_RUNBOOK.md`](GITEA_TOKEN_HOME_RUNBOOK.md) | Gitea 토큰 관리 런북 | | [`docs/GITEA_TOKEN_HOME_RUNBOOK.md`](GITEA_TOKEN_HOME_RUNBOOK.md) | Gitea 토큰 관리 런북 |
| [`spec/00_execution_contract.yaml`](../spec/00_execution_contract.yaml) | 실행 계약 원본 권위 | | [`spec/00_execution_contract.yaml`](../spec/00_execution_contract.yaml) | 실행 계약 원본 권위 |
| [`governance/agents_index.yaml`](../governance/agents_index.yaml) | 거버넌스 규칙 인덱스 | | [`governance/agents_index.yaml`](../governance/agents_index.yaml) | 거버넌스 규칙 인덱스 |
@@ -41,7 +41,7 @@
1. `GatherTradingData.json`이 있으면 그 파일을 우선 사용한다. 1. `GatherTradingData.json`이 있으면 그 파일을 우선 사용한다.
2. JSON이 없고 workbook 변환이 필요하면 `tools/convert_xlsx_to_json.py`를 별도 seed-prep 단계에서 실행한다. 2. JSON이 없고 workbook 변환이 필요하면 `tools/convert_xlsx_to_json.py`를 별도 seed-prep 단계에서 실행한다.
3. `docs/archive/ROADMAP_WBS.md`(2026-07-30 archive됨)의 WBS-8.2를 따른다. 3. `docs/ROADMAP_WBS.md`의 WBS-8.2를 따른다.
4. `tools/validate_platform_transition_wbs_v1.py``tools/validate_snapshot_admin_web_v1.py`를 확인한다. 4. `tools/validate_platform_transition_wbs_v1.py``tools/validate_snapshot_admin_web_v1.py`를 확인한다.
5. KIS 토큰은 `src/quant_engine/kis_api_client_v1.py`가 SQLite 캐시로 관리하므로, 수집 재실행 시에도 토큰을 매번 새로 발급하지 않는다. 5. KIS 토큰은 `src/quant_engine/kis_api_client_v1.py`가 SQLite 캐시로 관리하므로, 수집 재실행 시에도 토큰을 매번 새로 발급하지 않는다.
6. 토큰 상태는 `python tools/inspect_kis_token_cache_v1.py`로 확인한다. 6. 토큰 상태는 `python tools/inspect_kis_token_cache_v1.py`로 확인한다.
+445
View File
@@ -0,0 +1,445 @@
# QuantEngine 냉정 분석 & 마스터피스 로드맵
**분석 기준일: 2026-07-28 | 분석 범위: 전체 프로젝트 (spec/src/tools/docs/CI/CD/서버)**
---
## 🔬 Part 1: 냉정한 현실 진단
### 1.1 프로젝트 정체성 (What Is This?)
| 질문 | 냉정한 답변 |
|:---|:---|
| **이 프로젝트는 무엇인가?** | 은퇴자산 포트폴리오(~5억원)를 운용하는 결정론적 퀀트 투자 엔진. GAS→Python→.NET→Vue 3으로 점진 진화 중 |
| **누가 사용하는가?** | 현재 1인(본인). 미래 확장 가능성은 있으나 현재는 개인 운용 |
| **실제 동작하는가?** | GAS+Python 파이프라인은 **실제 운용 중** (98단계 DAG, KIS 연동, 리밸런싱 엔진). .NET+Vue 3은 어드민/대시보드 수준에서 동작 |
| **수익을 내는가?** | 아직 미측정. T+20 실측 데이터가 0건(DATA_GATED). 핵심 캘리브레이션 0/191 검증됨 |
### 1.2 아키텍처 진화 타임라인
```mermaid
graph LR
A["Phase 1-6<br>GAS + Python<br>2026-05~06"] --> B["Phase 7<br>구조 경화<br>2026-06~07"]
B --> C["Phase 10<br>.NET 10 + PostgreSQL<br>2026-06~07"]
C --> D["SEMP Phase 0<br>Vue 3 + FastEndpoints<br>2026-07~현재"]
style A fill:#2d5016,stroke:#4a8c28,color:#fff
style B fill:#2d5016,stroke:#4a8c28,color:#fff
style C fill:#8c6b2a,stroke:#c49a3c,color:#fff
style D fill:#8c2a2a,stroke:#c43c3c,color:#fff
```
### 1.3 기술 스택 현황 (냉정 평가)
| 레이어 | 기술 | 코드량 | 성숙도 | 냉정 평가 |
|:---|:---|:---|:---|:---|
| **데이터 수집** | GAS (18 `.gs`) + Python (KIS/Naver/Yahoo) | ~8,000 LOC | ⭐⭐⭐⭐ | ✅ **가장 안정적**. 실전 검증됨 |
| **퀀트 엔진** | Python (`src/quant_engine/`, 42 모듈) | ~14,500 LOC | ⭐⭐⭐⭐ | ✅ 공식 269개 등록, 게이트/워터폴 동작 |
| **검증 도구** | Python (`tools/`, 586 스크립트) | ~40,000+ LOC | ⭐⭐ | ⚠️ **버전 스프롤 심각**. v1~v6 난립, 정리 필요 |
| **백엔드 API** | .NET 10 / ASP.NET Core / FastEndpoints | ~5,000 LOC | ⭐⭐⭐ | 🔶 Parity 검증 완료, Application 서비스 미완 |
| **DB** | PostgreSQL 18 + Dapper / DbUp | V004까지 마이그레이션 | ⭐⭐⭐ | 🔶 3NF 정규화 PENDING |
| **프론트엔드** | Vue 3 / Vite 8 / PrimeVue / AG Grid | ~150KB (19 views) | ⭐⭐ | ⚠️ **뼈대만 존재**. 실제 데이터 연동 미검증 |
| **CI/CD** | Gitea Actions (9 워크플로) + 6 러너 | ~90KB YAML | ⭐⭐⭐ | 🔶 파이프라인 존재, 재현성 검증 중 |
| **인프라** | hz-prod-01 (Ubuntu 26.04, 2vCPU/3.7G) | systemd + Nginx | ⭐⭐⭐ | 🔶 동작하나 모니터링/알림 부재 |
### 1.4 핵심 문제점 — 5대 구조적 약점
> [!CAUTION]
> 이 프로젝트의 가장 큰 위험은 **"완료 표시가 많지만 실증이 없다"**는 것입니다.
#### 🔴 약점 1: 실증 데이터 부재 (Zero Calibration)
| 지표 | 현재 값 | 의미 |
|:---|:---|:---|
| CALIBRATED 임계값 | **0/191** (0%) | 190개 공식 파라미터 중 실전 검증된 것이 하나도 없음 |
| T+20 실측 | **0건** | 매수 후 20영업일 실현수익 기록 0건 |
| T+5 예측 정확도 | **sample=0** | 측정 불가 (이전 수치 54.76%/35.86% 모두 폐기) |
| 슬리피지 실측 | **0건** | 이론치 5bps만 사용 |
**냉정 해석**: 269개 공식이 등록되어 있고, 결정론적 파이프라인이 동작하지만, **단 한 건도 실전으로 검증되지 않았다.** 이 엔진은 사실상 "정교한 시뮬레이터"이지 "검증된 투자 엔진"이 아니다.
#### 🔴 약점 2: 기술 스택 분산 (Four Language Overhead)
```
GAS (.gs) ←→ Python (.py) ←→ C# (.cs) ←→ TypeScript (.ts/.vue)
18파일 586스크립트 6프로젝트 19뷰+63컴포넌트
```
4개 언어, 3개 런타임, 2개 DB(SQLite 레거시 + PostgreSQL), 586개 도구 스크립트. **1인 운영자에게 이 복잡도는 지속 가능하지 않다.**
#### 🟠 약점 3: tools/ 버전 스프롤
`tools/` 디렉토리에 **586개 스크립트**가 존재한다. 상당수가 `_v1`, `_v2`, `_v3` 등의 버전 접미사를 가지며, 어떤 것이 현재 canonical인지 즉시 판별하기 어렵다.
#### 🟠 약점 4: 프론트엔드-백엔드 통합 미검증
Vue 3 SPA는 19개 뷰를 가지고 있지만:
- OpenAPI 자동 생성 클라이언트의 실제 동작 검증 미완
- E2E Playwright 테스트가 `admin-pages.spec.ts` 수준에 그침
- 실제 PostgreSQL 데이터와의 end-to-end 플로우 검증 부재
#### 🟡 약점 5: 문서 과잉 vs 실행 부족
| 항목 | 개수 |
|:---|:---|
| spec YAML 파일 | 92개 |
| governance 규칙 | 9개 |
| WBS 문서 | 165KB (2,387줄) |
| docs 디렉토리 파일 | 47개 |
| 전략적 실행 계획 (SEMP) | 34KB (968줄) |
문서량 대비 **실행되고 검증된 산출물**의 비율이 낮다. "계약은 많고 체결은 적다."
---
### 1.5 강점 — 인정할 것
> [!TIP]
> 이 프로젝트가 가진 강점도 냉정히 인정해야 한다.
| 강점 | 근거 |
|:---|:---|
| **결정론적 아키텍처** | 269개 공식 ID + lifecycle 100% 등록 + 황금 테스트 커버리지 100% |
| **안전 게이트** | KIS API 거래 차단(governance/rules/06-07) — 코드 수준 강제 |
| **자체 비판 문화** | 2026-06-21 비판적 리뷰(0c절)에서 10건의 문제를 스스로 발견하고 추적 |
| **CI/CD 기반** | Gitea Actions 9개 워크플로, 6 러너, 자동 배포 + 롤백 |
| **클라우드 인프라** | hz-prod-01에 실제 배포, systemd + Nginx + PostgreSQL 운영 |
| **Parity 검증** | Python↔C# 계산기 40건 parity PASS |
| **spec 체계** | 92개 YAML spec — 의사결정 추적 가능성이 매우 높음 |
---
## 🎯 Part 2: 마스터피스를 위한 전략적 재구성
### 2.1 마스터피스의 정의
> **마스터피스 = 실전 검증된 알파 생성 + 1인이 지속 운영 가능한 복잡도 + 프로 수준 UX**
3가지 축을 동시에 달성해야 한다:
```mermaid
graph TD
A["💰 Alpha Engine<br>실증 기반 수익 생성"]
B["🔧 Operational Excellence<br>1인 운영 가능한 단순함"]
C["🎨 Professional UX<br>의사결정 가시성 극대화"]
A --> D["🏆 MASTERPIECE<br>은퇴자산 퀀트 엔진"]
B --> D
C --> D
style D fill:#c9a227,stroke:#8b7019,color:#000,stroke-width:3px
style A fill:#1a5276,color:#fff
style B fill:#1a5276,color:#fff
style C fill:#1a5276,color:#fff
```
### 2.2 전략적 페이즈 재구성
기존 Phase 0~10의 WBS는 너무 분산되어 있다. **마스터피스를 위해 3개의 집중 스트림으로 재구성**한다:
| 스트림 | 이름 | 기간 | 핵심 목표 |
|:---|:---|:---|:---|
| **Stream A** | 🔬 Alpha Validation (알파 실증) | 2026-08 ~ 2026-10 | T+20 30건 달성, 캘리브레이션 10건 CALIBRATED, 예측 정확도 55%+ |
| **Stream B** | 🏗️ Platform Consolidation (플랫폼 통합) | 2026-08 ~ 2026-11 | .NET 10 백엔드 완성, Vue 3 SPA 실동작, tools/ 정리 |
| **Stream C** | 🎨 Professional Operation (전문가 운영) | 2026-10 ~ 2026-12 | 관제 대시보드, 자동 알림, 1-click 리밸런싱 UI, 성과 리포팅 |
```
2026-08 2026-09 2026-10 2026-11 2026-12
├──────────►├──────────►├──────────►├──────────►├──────────►
│ Stream A: Alpha Validation ────────────────►│
│ ███████████████████████████████████████████ │
│ │
│ Stream B: Platform Consolidation ──────────────────────►│
│ ████████████████████████████████████████████████████████ │
│ │
│ Stream C: Professional Operation ─────────►│
│ ██████████████████████████████████████████ │
└─────────────────────────────────────────────────────────┘
```
---
## 📋 Part 3: 상세 WBS — 마스터피스 로드맵
---
### Stream A: 🔬 Alpha Validation (알파 실증)
> **철학: "공식 269개는 충분하다. 이제 1개라도 실전에서 증명하라."**
#### WBS-A1: T+20 실측 파이프라인 가동 (2026-08 Week 1~2)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| A1.1 | `build_operational_t20_outcome_ledger_v1.py` 일일 자동 실행 스케줄 등록 (Gitea Actions cron) | 없음 | `.gitea/workflows/t20_ledger.yml` 존재, cron 17:00 KST |
| A1.2 | 매수 진입 이벤트 자동 캡처 — KIS 체결내역 조회 또는 HTS 수동 기록 UI | A1.1 | `Temp/t20_entry_events.json` 행 수 ≥ 1 |
| A1.3 | T+20 만기 시점 자동 Close 가격 수집 — yfinance/KIS fallback | A1.2 | `Temp/t20_outcomes.json``close_t20` 필드 non-null |
| A1.4 | 30건 도달 시 `ALPHA_FEEDBACK_LOOP_V2` 자동 활성화 트리거 | A1.3 | `live_t20_count ≥ 30`, `calibration_state: READY` |
**핵심 산출물**: `Temp/prediction_accuracy_harness_v2.json``t20_sample ≥ 30`
---
#### WBS-A2: 캘리브레이션 실증 전환 1차 (2026-08 Week 3 ~ 2026-09 Week 2)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| A2.1 | `calibration_priority_v1.json`에서 urgency score 상위 20건 추출 | 없음 | 대상 목록 JSON 존재 |
| A2.2 | 20건에 대해 과거 1년 역사 데이터 백테스트 (replay calibration) | A2.1 | `Temp/replay_calibration_results_v1.json` gate: PASS |
| A2.3 | 백테스트 결과 기반 10건 `EXPERT_PRIOR/SPEC_DERIVED``CALIBRATED` 승격 | A2.2 | `spec/calibration_registry.yaml``source: CALIBRATED` 10건+ |
| A2.4 | 승격된 임계값으로 엔진 재실행, 결과 비교 (before/after) | A2.3 | `Temp/calibration_impact_report_v1.json` 존재 |
**핵심 산출물**: CALIBRATED ≥ 10/191 (5.2%+ 달성)
---
#### WBS-A3: 예측 정확도 목표 달성 (2026-09 ~ 2026-10)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| A3.1 | T+20 30건 기반 첫 match_rate 산출 | A1.4 | `match_rate_pct ≥ 50%` (1차 목표) |
| A3.2 | SS001 가중치(P/V/F) 1차 재보정 | A3.1 | `Temp/alpha_calibration_v1.json` — 보정 전후 개선 ≥ 2%p |
| A3.3 | 슬리피지 실측 5건 기록 (HTS 체결 후 수동 입력) | 없음 | `outputs/execution_slippage.db` sample ≥ 5 |
| A3.4 | 슬리피지 실측값 vs 5bps 이론값 비교 및 spec 갱신 | A3.3 | `gap_bps` 보고서 존재, 3bps 초과 시 spec 갱신 |
| A3.5 | 섹터 플로우 30일 달성 후 `FLOW_CREDIT_V1` 활성화 | 없음 | `days_accumulated ≥ 30`, lifecycle → ACTIVE |
**핵심 산출물**: `match_rate_pct ≥ 55%`, `honest_proof_score ≥ 70`
---
### Stream B: 🏗️ Platform Consolidation (플랫폼 통합)
> **철학: "복잡도를 줄여라. 1인이 유지할 수 없는 구조는 마스터피스가 아니다."**
#### WBS-B1: tools/ 대정리 (2026-08 Week 1~2)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| B1.1 | `tools/` 586개 파일 전수 인벤토리 — canonical/deprecated/dead 3등급 분류 | 없음 | `Temp/tools_inventory_v1.json` 생성 |
| B1.2 | Dead 스크립트 → `tools/archive/` 이동 (삭제하지 않고 보존) | B1.1 | `tools/archive/` 100건+ 이동 |
| B1.3 | Canonical 스크립트에 `#!/usr/bin/env python` + docstring 표준화 | B1.2 | canonical 스크립트 100% docstring 보유 |
| B1.4 | `tools/README.md` — canonical 도구 목록 + 사용법 작성 | B1.3 | README 존재, 검증 명령 포함 |
| B1.5 | GAS 중복 정리: `src/gas/` + `src/gas_adapter_parts/` + `src/google_apps_script/``src/gas/` 단일화 | 없음 | 3개 디렉토리 → 1개로 통합 |
| B1.6 | `src/client/` 레거시 삭제 | 없음 | 디렉토리 미존재 |
| B1.7 | `.gitea/workflows/deploy-prod.yml.backup` 삭제 | 없음 | 파일 미존재 |
**핵심 산출물**: `tools/` 파일 수 300개 이하, canonical 도구 목록 문서
---
#### WBS-B2: .NET 10 백엔드 완성 (2026-08 Week 3 ~ 2026-09)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| B2.1 | Application 서비스 완성 — Workspace/Approval/Collection/Formula 4개 서비스 실구현 | 없음 | `dotnet test --filter ApplicationService` 13+ PASS |
| B2.2 | 데이터 수집 오케스트레이터 — KIS-first → Naver fallback → JSON replay | B2.1 | `dotnet test --filter Collection` 4+ PASS |
| B2.3 | PostgreSQL 3NF 스키마 정규화 (V005~V008 마이그레이션) | 없음 | `dotnet-ef database update` 성공, `stocks`/`sources`/`market_data` 테이블 존재 |
| B2.4 | Repository 패턴 100% 적용 — Dapper + interface 기반 | B2.3 | 직접 SQL 호출 0건 (Service 레이어에서) |
| B2.5 | FastEndpoints API 완성 — 최소 15개 엔드포인트 (CRUD + 퀀트 결과 조회) | B2.1 | OpenAPI spec 자동 생성, endpoint 15개+ 존재 |
| B2.6 | Hangfire 스케줄러 — 일일 수집 + 주간 리밸런싱 + 월간 유니버스 갱신 | B2.2 | Hangfire 대시보드에서 3개 recurring job 확인 |
| B2.7 | 보안 강화 — BCrypt 패스워드 해싱, JWT 토큰 갱신, CSRF 방어 완전 탑재 | B2.5 | `dotnet test --filter Security` 10+ PASS |
**핵심 산출물**: `dotnet build` 경고 0, `dotnet test` 250+ PASS, API 엔드포인트 15+
---
#### WBS-B3: Vue 3 SPA 실동작 검증 (2026-09 ~ 2026-10)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| B3.1 | OpenAPI TypeScript 클라이언트 자동 생성 + Axios interceptor 완성 | B2.5 | `src/frontend/src/api/generated/` 자동 생성, `npm run type-check` 0 에러 |
| B3.2 | 로그인 플로우 — JWT + Refresh Token + 자동 갱신 | B2.7 | Playwright E2E: 로그인→토큰갱신→인증만료 시나리오 PASS |
| B3.3 | DashboardView — 실시간 포트폴리오 요약 (PostgreSQL 데이터 연동) | B3.1 | DashboardView에서 총자산/수익률/포지션 데이터 렌더링 확인 |
| B3.4 | SnapshotAdminView — account_snapshot 편집/검증/저장/승인 4단계 플로우 | B3.1 | Playwright E2E: 편집→저장→diff preview→승인 시나리오 PASS |
| B3.5 | DatabaseView — AG Grid 기반 전체 테이블 브라우저 | B3.1 | 10,000행 렌더링 성능 P95 < 200ms |
| B3.6 | SystemSettingsView — 전체 시스템 설정 관리 UI 실연동 | B3.1 | 설정 변경 → DB 반영 → 화면 갱신 round-trip |
| B3.7 | Vitest 단위 테스트 20+ 작성, Playwright E2E 10+ 시나리오 | B3.2 | `npm run test:unit` 20+ PASS, `npm run test:e2e` 10+ PASS |
**핵심 산출물**: Vue 3 SPA 완전 동작, E2E 테스트 10+, 타입 에러 0
---
#### WBS-B4: CI/CD 파이프라인 통합 (2026-10)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| B4.1 | CI 파이프라인 단일화 — Python/dotnet/frontend 3-stage gate 직렬 | B3.7 | `ci.yml` 실행 시간 ≤ 20분 |
| B4.2 | 배포 파이프라인 — frontend build → dotnet publish → tar.gz → 배포 → 헬스체크 | B4.1 | `deploy-prod.yml` 자동 실행, 6개 헬스체크 PASS |
| B4.3 | 배포 후 Playwright smoke 테스트 — 운영 서버 접속 + 로그인 + 대시보드 확인 | B4.2 | `tests/e2e/production-smoke.spec.ts` PASS |
| B4.4 | CI 재현성 검증 — 3회 연속 실행 결과 100% 동일 | B4.1 | `Temp/ci_reproducibility_report.json` variance < 5% |
**핵심 산출물**: 단일 `git push` → 15~20분 내 자동 배포 + 검증 완료
---
### Stream C: 🎨 Professional Operation (전문가 운영)
> **철학: "마스터피스는 보는 순간 신뢰감을 준다."**
#### WBS-C1: 관제 대시보드 (2026-10 ~ 2026-11)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| C1.1 | **Portfolio Overview** — 총자산, 일일/주간/월간 수익률, KOSPI 대비 알파, MDD | B3.3 | 첫 화면에서 5초 내 전체 상황 파악 가능 |
| C1.2 | **Position Heat Map** — 종목별 손익 히트맵 + Core/Satellite/Cash 버킷 시각화 | B3.3 | 11개 포지션 히트맵 렌더링, 색상으로 건강도 즉시 인지 |
| C1.3 | **Signal Dashboard** — SS001 점수, 라우팅 게이트 상태, 매수/매도 신호 실시간 표시 | B3.3 | HOLD/SELL_READY/BLOCKED 상태 색상 chips 표시 |
| C1.4 | **Calibration Health** — 191개 임계값 중 CALIBRATED/PROVISIONAL/미검증 비율 진행바 | B3.3 | 캘리브레이션 건강도 게이지 차트 |
| C1.5 | **Engine Activity Log** — 최근 엔진 실행 이력, 성공/실패/경고 타임라인 | B3.3 | 최근 30일 실행 이력 스크롤 가능 |
**핵심 산출물**: 한 화면에서 포트폴리오 건강도 + 신호 + 엔진 상태를 즉시 파악
---
#### WBS-C2: 자동 알림 & 모니터링 (2026-11)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| C2.1 | Telegram Bot 알림 — 일일 엔진 실행 결과, 매도 신호 발생, MDD 경고 | 없음 | Telegram 메시지 수신 확인 |
| C2.2 | 장애 자동 감지 — CI 실패, 서버 다운, 데이터 수집 중단 시 즉시 알림 | C2.1 | 의도적 장애 주입 → 5분 내 알림 수신 |
| C2.3 | Serilog + OpenTelemetry 구조화 로깅 — JSON 형식 로그 + 메트릭 수집 | B2.5 | `journalctl -u quantengine` JSON 구조 확인 |
| C2.4 | 주간 자동 리포트 — 포트폴리오 성과, 신호 변화, 캘리브레이션 진척 | C1.1 | 매주 일요일 Telegram 리포트 수신 |
**핵심 산출물**: 수동 확인 없이 이상 상황 자동 통보
---
#### WBS-C3: 1-Click 리밸런싱 워크플로 (2026-11 ~ 2026-12)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| C3.1 | 리밸런싱 제안 화면 — 버킷 밴드 위반 시 자동 제안, 주문표 생성 | C1.2 | 리밸런싱 필요 시 자동 제안 카드 표시 |
| C3.2 | 주문 시뮬레이션 — 지정가/호가단위 정규화 결과 미리보기 | C3.1 | 시뮬레이션 결과 테이블 (ticker/수량/지정가/슬리피지) |
| C3.3 | 승인 → HTS 주문표 export (CSV/클립보드) | C3.2 | "승인" 버튼 → CSV 다운로드/클립보드 복사 |
| C3.4 | 체결 후 실측 기록 UI — 의도가/실제체결가 입력 → 슬리피지 DB 저장 | C3.3 | `execution_slippage` 레코드 생성 확인 |
**핵심 산출물**: 리밸런싱 판단 → 주문 → 체결 기록의 완전한 루프
---
#### WBS-C4: 성과 리포팅 & 아카이빙 (2026-12)
| WBS | 작업 | 선행 | 성공 판단 데이터 |
|:---|:---|:---|:---|
| C4.1 | 월간 성과 보고서 자동 생성 — PDF/마크다운 (수익률, 알파, MDD, 매매 이력) | C1.1 | `Temp/monthly_report_2026_12.pdf` 존재 |
| C4.2 | 벤치마크 비교 차트 — KOSPI, S&P 500 대비 누적 수익률 | C4.1 | 차트에서 3개 라인 비교 가능 |
| C4.3 | 연간 결산 보고서 — 세금 계산, 배당 수입, 실현/미실현 손익 | C4.1 | 연간 보고서 항목 100% 채움 |
| C4.4 | 이력 아카이빙 — 일일 포트폴리오 스냅샷 PostgreSQL 시계열 저장 | B2.3 | `portfolio_daily_snapshots` 테이블 행 수 ≥ 30 |
**핵심 산출물**: 전문가급 성과 리포트 자동 생성
---
## 📊 Part 4: 완성도 매트릭스 & 마일스톤
### 4.1 마스터피스 완성도 KPI (2026-12-31 목표)
| 차원 | 지표 | 현재 | 마스터피스 목표 | 판정 |
|:---|:---|:---|:---|:---|
| **Alpha** | T+20 실측 건수 | 0 | ≥ 50 | 🔴 |
| **Alpha** | CALIBRATED 임계값 | 0/191 | ≥ 30/191 (15%+) | 🔴 |
| **Alpha** | 예측 정확도 (match_rate) | DATA_GATED | ≥ 55% | 🔴 |
| **Alpha** | 슬리피지 실측 | 0건 | ≥ 10건 | 🔴 |
| **Alpha** | KOSPI 대비 알파 | 미측정 | > 0%p/분기 | 🔴 |
| **Platform** | .NET 테스트 | 214 | ≥ 300 | 🟠 |
| **Platform** | Vue 3 E2E 테스트 | ~3 | ≥ 15 | 🟠 |
| **Platform** | tools/ 파일 수 | 586 | ≤ 200 (canonical) | 🟠 |
| **Platform** | CI 재현성 | 미검증 | 3회 연속 100% 동일 | 🟠 |
| **Platform** | 배포 소요시간 | 수동 | ≤ 20분 (자동) | 🟠 |
| **Operation** | 수동 개입 | 매일 | ≤ 1회/주 | 🟡 |
| **Operation** | 장애 알림 | 없음 | 5분 내 Telegram | 🔴 |
| **Operation** | 월간 리포트 | 없음 | 자동 생성 | 🔴 |
| **Operation** | 리밸런싱 워크플로 | CLI 전용 | 웹 UI 1-Click | 🔴 |
### 4.2 월별 마일스톤
| 월 | 마일스톤 | 핵심 증빙 |
|:---|:---|:---|
| **2026-08** | **M1: Alpha Pipeline Live** — T+20 수집 자동화 + tools/ 대정리 완료 | T+20 entry 10건+, tools/ 300개 이하 |
| **2026-09** | **M2: Backend Complete** — .NET Application 서비스 + PostgreSQL 3NF + API 15개 | `dotnet test` 250+, endpoint 15+ |
| **2026-10** | **M3: SPA Live** — Vue 3 전체 뷰 실동작 + CI/CD 통합 | E2E 10+, 자동 배포 동작 |
| **2026-11** | **M4: Professional Ops** — 관제 대시보드 + Telegram 알림 + 리밸런싱 UI | 대시보드 5개 패널, 알림 동작 |
| **2026-12** | **M5: Masterpiece** — 알파 실증 + 성과 리포트 + 연간 결산 | match_rate ≥ 55%, 월간 리포트 자동 |
---
## 🔥 Part 5: 즉시 실행 — Sprint-0 (이번 주, 2026-07-28 ~ 2026-08-01)
> **이번 주에 할 수 있는 가장 가치 있는 4가지**
### Sprint-0.1: T+20 수집 자동화 파이프라인 (Day 1~2)
```bash
# 1. Gitea Actions 일일 cron 등록
# .gitea/workflows/t20_ledger.yml
# 매 영업일 17:00 KST 자동 실행
python tools/build_operational_t20_outcome_ledger_v1.py --auto
```
- 이것이 **가장 시급**하다. 알파 검증의 전제조건이 데이터 누적이고, 하루라도 빨리 시작해야 한다.
### Sprint-0.2: tools/ 파일 인벤토리 자동 분류 (Day 2~3)
```bash
python tools/build_tools_inventory_v1.py
# 586개 → canonical / deprecated / dead 3등급 분류
# Temp/tools_inventory_v1.json 산출
```
### Sprint-0.3: 슬리피지 실측 첫 기록 (Day 3~4)
```bash
# 최근 체결 이력에서 1건이라도 기록
python tools/evaluate_execution_slippage_v1.py record \
--ticker 005930 --side BUY \
--intended-price 71000 --actual-price 71050 \
--recorded-at 2026-07-28
```
### Sprint-0.4: 레거시 파일 정리 (Day 4~5)
```bash
# 즉시 삭제 가능한 레거시
rm -rf src/client/
rm .gitea/workflows/deploy-prod.yml.backup
```
---
## 📐 Part 6: 의존성 차트 (전체)
```mermaid
graph TD
subgraph "Stream A: Alpha Validation"
A11["A1: T+20 파이프라인"]
A21["A2: 캘리브레이션 실증"]
A31["A3: 예측 정확도"]
A11 --> A31
A21 --> A31
end
subgraph "Stream B: Platform"
B11["B1: tools 정리"]
B21["B2: .NET 완성"]
B31["B3: Vue 3 SPA"]
B41["B4: CI/CD 통합"]
B21 --> B31
B31 --> B41
end
subgraph "Stream C: Operation"
C11["C1: 관제 대시보드"]
C21["C2: 자동 알림"]
C31["C3: 리밸런싱 UI"]
C41["C4: 성과 리포팅"]
B31 --> C11
C11 --> C21
C11 --> C31
C31 --> C41
end
A31 --> C41
```
---
> [!IMPORTANT]
> **마스터피스의 핵심은 기술이 아니라 실증이다.**
>
> 269개 공식, 92개 spec, 586개 도구 — 이 모든 것은 **T+20 30건이 쌓이고, 캘리브레이션 10건이 CALIBRATED되고, match_rate가 55%를 넘는 순간** 비로소 의미를 갖는다.
>
> 지금 이 순간부터 가장 중요한 것은 **데이터 누적**이다. 하루라도 빨리 T+20 파이프라인을 돌려야 한다.
@@ -1,3 +1,12 @@
> **정정 (2026-07-30)**: 이 문서는 잠시 `docs/archive/`로 옮겨졌다가 같은 날 다시 원래 위치로
> 복구되었다. `tools/validate_enterprise_crud_specification_v1.py:29`가 이 경로
> (`docs/ROADMAP_ENTERPRISE_TEMPLATES_WBS.md`)를 직접 참조하고, 이 검증은
> `.gitea/workflows/ci-frontend.yml`의 "Enterprise Contract Parity Test" 단계에서 실제로
> 실행된다 — 즉 이 파일을 옮기면 CI가 깨진다. 내용상 권위는
> [`../spec/60_oms_wms_erp_wbs.yaml`](../spec/60_oms_wms_erp_wbs.yaml) 또는
> [`OMS_WMS_ERP_PLAYBOOK.md`](OMS_WMS_ERP_PLAYBOOK.md)에 있을 수 있지만, 파일 자체는
> 검증기가 요구하는 이 경로에 계속 있어야 한다.
# OMS·WMS·ERP 입력 컴포넌트 & 공통 CRUD 템플릿 & 상용화 제안 마스터 WBS (WBS-MASTER-2026) # OMS·WMS·ERP 입력 컴포넌트 & 공통 CRUD 템플릿 & 상용화 제안 마스터 WBS (WBS-MASTER-2026)
## 0. 개요 및 3대 명세 통합 권위 ## 0. 개요 및 3대 명세 통합 권위
@@ -1,3 +1,11 @@
> **정정 (2026-07-30)**: 이 문서는 잠시 `docs/archive/`로 옮겨졌다가 같은 날 다시 원래 위치로
> 복구되었다. 내용 자체는 2026-06-13 기준으로 오래됐지만(최신 마이그레이션 상태는
> [`../CLAUDE.md`](../CLAUDE.md) Migration Status 참고), `tools/validate_quant_engine_wbs_v1.py`,
> `tools/validate_platform_transition_wbs_v1.py`, `tests/unit/test_validate_quant_engine_wbs_v1.py`,
> `spec/60_quant_engine_wbs.yaml` 등 release gate 검증 스크립트/스펙이 이 경로(`docs/ROADMAP_WBS.md`)의
> 존재와 특정 내용을 실제로 검사하므로, "오래됐다"는 이유만으로 archive하면 검증 파이프라인이 깨진다.
> 문서 정리가 필요하면 이 파일을 지우거나 옮기지 말고, 먼저 위 검증 스크립트들을 갱신해야 한다.
# 퀀트투자 엔진 — 전체 로드맵 & WBS & 하네스 성공 기준 # 퀀트투자 엔진 — 전체 로드맵 & WBS & 하네스 성공 기준
> 작성일: 2026-06-13 | 엔진 버전: REBALANCE_ENGINE_V1 기준 > 작성일: 2026-06-13 | 엔진 버전: REBALANCE_ENGINE_V1 기준
+135
View File
@@ -0,0 +1,135 @@
# 2026-07-30 QuantEngine 전략 감사 세션 — 인수인계
19개 원칙(SOLID, 정공법, 홀루시네이션, 재현성, 데이터정합성, 과유불급, 정규화/역정규화,
프로세스 단순화, 패턴화, 표준화, 구조화, 바이브코딩, 현장감, 이력성, 안정성, 고도화,
컴포넌트화, 기술부채) 기준으로 저장소를 감사하고 발견된 문제를 수정한 세션의 기록이다.
8개 커밋이 `main`에 반영·푸시됐다 (HEAD: `823a9a6`). 무엇이 바뀌었는지는 `git log`
권위 있는 출처이니 여기서 반복하지 않는다 — 이 문서는 **커밋 로그만 봐서는 알 수 없는
맥락**(미해결 항목, 판단 근거, 재발 방지 트랩)만 담는다.
## 미해결 / 다음에 이어갈 것
- **전체 release DAG(`npm run ops:validate` / `full-gate`)를 끝까지 검증하지 못함.**
`GatherTradingData.xlsx`(gitignore 대상, 실거래 데이터 시드)가 이 개발 환경에는 없어서
`convert_xlsx` 노드에서 막힌다. 이 파일이 있는 실제 환경(사용자 로컬 또는 운영 서버)에서
한 번 돌려서 이번 세션의 변경사항이 데이터 수집 경로를 깨지 않았는지 최종 확인 필요.
- **`docs/db/quantengine.dbml``engine_history` vs `quantengine` 스키마 동명 테이블 4개**
— 실제 .NET 호출부를 grep으로 대조해 "둘 다 살아있는 서로 다른 모델"로 확정하고 DBML에
근거를 남겼지만, 코드 통합/리네임은 하지 않았다. "정리해달라"는 요청이 오면 DBML의
`V8: PostgreSQL History-First Operating Model` 섹션 노트부터 다시 읽을 것 — 죽은 코드로
섣불리 판단하지 말 것.
- **OpenDART는 배선만 맞춰뒀고 실제로 호출하는 워크플로우가 아직 없다.** 환경변수명은
`OPENDART_OPENAPI_KEY`로 코드·Gitea Secrets 양쪽 통일 완료. 나중에
`tools/ingest_fundamental_raw.py`를 CI에서 처음 호출할 때는 별도 매핑 없이
`secrets.OPENDART_OPENAPI_KEY`를 그 job의 `env:`에 바로 연결하면 된다.
- **KRX Open API는 구현이 전혀 없다** — CLAUDE.md에 환경변수명(`KRX_OPENAPI_KEY`, 실제
Gitea Secrets 이름과 일치)만 예약해뒀다. 호출 한도 등은 사용자가 알려주지 않아 기록하지
않았다 — 추측해서 채우지 말 것.
## 파일 수 최종 현황 (같은 날, 3차 라운드)
`tests/unit/test_kis_data_collection_v1.py`가 F8과 같은 원인(deprecated/ 이동)으로 import가
깨져 있던 걸 발견해 고쳤다 (오늘 삭제와 무관, 이전부터 있던 버그 — `pytest --collect-only`
실제 실행해서 발견). 그리고 `tests/golden/generated/` 174개 — 스펙(QE-M0-06)이 스스로
"M3 패리티 대체 후 삭제" 조건을 걸어뒀고, M3-01~05가 전부 DONE인 걸 확인한 뒤 삭제. CI/커버리지
게이트 어디서도 이 파일들을 쓰지 않는다는 것까지 확인 완료. **최종 파일 수: 4,523 → 1,687**
(예산 2,200 대비 77%, 여유 513개). 이 지표는 이제 확실히 건강한 상태 — 추가로 파일 수를
줄이려는 시도는 과유불급이다(이미 통과한 지표를 더 깎으려다 실수로 쓰이는 파일을 지울 위험이
더 크다). `tools/`에는 아직 그레이존 파일이 좀 더 있을 수 있지만(74개 삭제 후 남은 것들은
전부 검토 완료), 더 뒤지는 것보다 실제 필요가 생겼을 때 개별적으로 판단하는 게 낫다.
## orphan 스크립트 정리 (같은 날, 2차 조사)
`tools/*.py` + `src/quant_engine/*.py` 619개 파일 전수 스캔(파일명이 저장소 어디에도 안 나오는
것 탐지) 결과 81개 orphan 후보 발견. 두 단계로 나눠 총 74개 삭제:
- 1차 34개: 명백한 버전 중복(`_v2`/`_correct`/`_properly` 짝) + tools/에 섞인 임시 테스트/디버그
스크립트.
- 2차 40개: WBS 티켓 전용 1회성 스크립트 + 기타 build_*/validate_* 진단 도구. **주의**: 스크립트
자체 이름 검색만으로는 부족했다 — 47개 후보 중 7개는 이름은 안 불려도 만들어내는
`Temp/*.json` 산출물이 다른 도구에서 계속 읽히고 있어 실제로는 살아있었다
(`build_outcome_ledger_v1.py`, `build_pre_distribution_early_warning_v3.py`,
`build_shadow_ledger_v1.py`, `build_p2_01_live_outcome_ledger.py`,
`build_p2_02_calibration_promotion.py`, `build_p1_01_execution_verdict_unify.py`,
`run_release_ci_gate_v2.py` — 이 7개는 삭제하지 않고 남겨둠). **다음에 orphan 스캔할 때는
스크립트명뿐 아니라 그 스크립트가 쓰는 `Temp/*.json` 출력 경로까지 같이 검색할 것.**
나머지 orphan 후보(그레이존 밖, 원래 81개에 없던 것들)는 손대지 않았다 — 전수조사가 아니라
`tools/`+`src/quant_engine/`만 스캔한 결과다.
## GatherTradingData.xlsx 관련 (같은 날, 추가 조사)
로컬에는 `.xlsx`가 없지만, 운영 서버(`~/QuantEngineByItz/GatherTradingData.json`)에는 이미
xlsx→json 변환이 끝난 정본 시드 파일이 있다 (저장소 자체 설계 문서
`docs/GATHERTRADINGDATA_XLSX_DECISION_2026-06-21.md`가 "json을 정본으로, xlsx는 미추적"으로
결정한 바로 그 파일). SCP로 로컬에 받아왔다(`./GatherTradingData.json`, gitignore 대상이라
커밋 안 됨). 그런데 `tools/run_release_dag_v3.py``convert_xlsx` 노드는 xlsx 파일이 실제로
없으면 무조건 하드 실패하도록 짜여 있어(캐시/스킵 로직 없음, 코드 주석에 "optional 최적화,
아직 미구현"이라고 명시됨), json이 이미 있어도 release DAG를 처음부터 끝까지 돌릴 수는 없다.
남은 WBS 태스크 10개(QE-M3-03, M4-01~05, M5-01~04)를 이 json만으로 복구하려 했으나:
- **M3-03**: `engine_history.factor_output_history`에 최근 24시간 데이터가 있어야 하는 라이브
신선도 게이트 — 데이터 유무와 무관, 운영 파이프라인이 최근 실제로 돌았는지에 달림.
- **M4-01~04, M5-01~03**: `Temp/backtest_result_v1.json` 등은 Python 스크립트가 아니라
**`src/dotnet/QuantEngine.Tools`/`QuantEngine.Web`를 실제로 띄우고 Playwright로 백테스트/
캘리브레이션 화면을 조작해야** 생성된다(M4-05 검증 커맨드가 `npx playwright test`
`verify_wbs_task_v1.py` 순서인 게 근거). 로컬 웹서버 기동 + Playwright 브라우저 설치 +
실제 가격/팩터 이력 DB 데이터 충분 여부까지 얽혀 있어 이번 세션에서는 시도하지 않았다.
- **M4-05, M5-04**: 위와 같은 이유로 막힘.
다음 세션에서 이어가려면: SSH 터널 + `dotnet watch run --project QuantEngine.Web` +
`npx playwright test --project=evidence tests/e2e/evidence/qe-m4-05-backtest.spec.ts` 순으로
시도. DB에 실제 가격 이력이 충분한지부터 확인할 것 (부족하면 백테스트 자체가 의미있는 결과를
못 낼 수 있음).
## 추가 발견 (같은 날, entropy 정리 이후)
- **`Temp/*` 전체 삭제가 WBS 검증 캐시를 깼다.** `tools/verify_wbs_task_v1.py`가 만드는
`Temp/evidence/<task_id>/verdict.json`과 각종 `Temp/*.json`(golden_coverage_100,
market_time_series_schema 등)은 git에 추적되지 않지만 "완료된 검증의 증적"이라 순수
빌드 캐시가 아니었다. `spec/60_quant_engine_wbs.yaml`의 13개 태스크가 FAIL로 나타났었음.
- 3개(QE-M0-06, QE-M2-06, QE-M2-01)는 해당 generator 재실행 + DB 재접속으로 복구 완료.
- QE-M3-03은 무관 — `engine_history.factor_output_history`에 최근 24시간 내 데이터가
있어야 하는 라이브 신선도 게이트. 오늘 세션과 상관없이 운영 파이프라인이 최근 안
돌았으면 원래도 FAIL이다.
- 나머지 7개(QE-M4-01~04, QE-M5-01~03)는 `Temp/prediction_accuracy_harness_v2.json`
등 실거래 데이터 파생 체인이 필요 — 이 저장소 스냅샷엔 `GatherTradingData.xlsx`
없어서(이미 위에서 언급한 그 문제) 복구 불가. 실거래 데이터가 있는 환경에서 전체
파이프라인을 한 번 돌리면 자동 복구된다.
- QE-M4-05/QE-M5-04(Playwright evidence)도 같은 체인에 의존해 사실상 같이 막혀있음.
- **교훈**: `Temp/`가 git 미추적이라고 전부 "순수 빌드 산출물"은 아니다 — `Temp/evidence/`처럼
"재실행하면 되지만 재실행에 실제 데이터/DB가 필요한 캐시"가 섞여 있다. 다음에 비슷한
정리를 할 때는 삭제 전에 `rg -l "Temp/" spec/ tools/` 정도로 어떤 하위 경로가 검증
체인에 물려있는지 먼저 확인할 것.
## 이번 세션에서 걸린 함정 — 재발 방지
- **문서를 "오래됐다"고 archive하기 전에 `tools/*.py`, `spec/*.yaml`, `tests/*.py`,
`.gitea/workflows/*.yml`까지 전부 grep할 것** — 다른 문서/CLAUDE.md만 검색해서는 부족하다.
`docs/ROADMAP_WBS.md``docs/ROADMAP_ENTERPRISE_TEMPLATES_WBS.md`를 이 방식으로 archive
했다가, 실제로는 release gate 검증기와 `ci-frontend.yml`의 Enterprise Contract Parity
Test가 그 경로를 직접 참조하고 있어서 되돌려야 했다. 지금은 이 두 파일 다시 원위치, 검증기
3개 직접 실행해서 통과 확인 완료.
- **DbUp 마이그레이션은 파일명을 순수 문자열로 정렬한다** — `V10__``V2__`보다 먼저
정렬되는 문제가 있었다. `MigrationScriptNameComparer`(이번 세션에 추가, `DbMigrator.cs`
연결)로 해결됨 — 새 마이그레이션은 zero-padding 없이 다음 정수만 쓰면 된다.
- **`runtime/`은 대부분 git 추적되는 이력 데이터다** (`refactor_baseline_v*.yaml`,
`rollback_manifest_v*.yaml` 등) — 스크래치 공간이 아니다. 확인용 명령의 `--out`
실수로 이 경로로 잡아서 이력 파일 2개를 덮어썼다가 `git diff`로 발견하고
`git checkout --`으로 복구했다. 1회성 도구 출력은 `/tmp/` 등으로 보낼 것.
- **이 머신에 Python이 두 개 설치돼 있다** — `python`(3.11, 대부분의 서브프로세스가
실제로 쓰는 것)과 `python3`(3.14). 한쪽에 패키지를 설치해도 다른 쪽엔 없다.
- **repository entropy 감사(`audit_repository_entropy_v2.py`)가 git 추적 파일이 아니라
로컬 디스크 전체를 세고 있었다** — `.gitignore`와 제외 목록이 어긋나 있어 로컬 빌드 한 번에
4,523개까지 부풀었다(예산 2,200). `.gitignore`와 일치하도록 제외 목록을 갱신해 재발을
막았다 (`tools/audit_repository_entropy_v1.py`). 이 게이트가 다시 실패하면 먼저 "새로운
미추적 빌드 산출물 디렉터리가 생겼나"부터 확인할 것 — 바로 대규모 파일 삭제로 가지 말 것.
## 이번 세션에서 확립된 작업 방식
- 작업 라운드가 끝나면 다음에 할 만한 구체적인 후보를 먼저 제안한다 (사용자가 같은 지시를
반복해서 보내는 걸 방지).
- 단순 기계적 조사/위임은 `model: "haiku"`로, 판단이 필요한 작업만 Sonnet급으로.
- 커밋/푸시는 명시적으로 요청받았을 때만, 항상 경로를 지정해서 스테이징(`git add -A` 금지),
세션 시작 전부터 있던 무관한 변경사항은 요청 없이는 포함하지 않는다.
- 채팅에 붙여넣어진 실제 API 키(KIS, OpenDART, KRX)는 어떤 파일에도 적지 않는다 — 환경변수
*이름*만 기존 KIS 관례(환경변수/Gitea Secrets 전용, 하드코딩 금지)에 맞춰 기록한다.
@@ -1,6 +1,6 @@
# WBS-10 보강: .NET Core 마이그레이션 완성 & 상용화 로드맵 (2026-06-30) # WBS-10 보강: .NET Core 마이그레이션 완성 & 상용화 로드맵 (2026-06-30)
> 본 문서는 [docs/archive/ROADMAP_WBS.md](./archive/ROADMAP_WBS.md) (2026-07-30 archive됨) 의 **WBS-10(.NET 엔진 고도화)** 을 현 시점 실측 기준으로 재진단하고, 마이그레이션 완성과 단일 사용자 상용 운영에 필요한 잔여 작업을 재정의한다. > 본 문서는 [docs/ROADMAP_WBS.md](./ROADMAP_WBS.md) 의 **WBS-10(.NET 엔진 고도화)** 을 현 시점 실측 기준으로 재진단하고, 마이그레이션 완성과 단일 사용자 상용 운영에 필요한 잔여 작업을 재정의한다.
> >
> **작성 배경:** 기존 WBS-10 의 다수 항목이 `완료` 로 표기되어 있으나, 2026-06-30 소스 실측 결과 **표기와 실제 상태 간 괴리**가 확인되었다. 본 문서는 그 괴리를 정리하고 실제 잔여 작업을 추적한다. > **작성 배경:** 기존 WBS-10 의 다수 항목이 `완료` 로 표기되어 있으나, 2026-06-30 소스 실측 결과 **표기와 실제 상태 간 괴리**가 확인되었다. 본 문서는 그 괴리를 정리하고 실제 잔여 작업을 추적한다.
> >
@@ -49,7 +49,7 @@
- 배경 설명 문서 - 배경 설명 문서
- 의사결정 근거 - 의사결정 근거
- 최신화 필요 - 최신화 필요
- 예: docs/archive/ROADMAP_WBS.md (2026-07-30 archive됨) - 예: docs/ROADMAP_WBS.md
**Deprecated (신뢰도 0%)** **Deprecated (신뢰도 0%)**
- 폐기된 알고리즘 - 폐기된 알고리즘
+40
View File
@@ -0,0 +1,40 @@
version: "1.0.0"
objective: "oms-wms-erp의 계약 중심 리팩토링을 안전한 증분 단위로 수행"
scope:
canonical_code_root: "oms-wms-erp"
excluded_roots:
- "src/frontend"
- "Temp"
- "archive"
work_items:
- id: "WBS-20260728-01"
title: "baseline 및 계약 경계 확인"
status: "in_progress"
success_data:
- "npm run type-check exit code 0"
- "npm run build exit code 0"
- "npm run test exit code 0"
- "python tools/validate_enterprise_crud_specification_v1.py exit code 0"
- id: "WBS-20260728-02"
title: "공통 계약·타입·오류 처리의 단일 진실원 확립"
status: "pending"
success_data:
- "중복 API 오류·감사·그리드 계약 0건"
- "기존 소비자 타입체크 통과"
- id: "WBS-20260728-03"
title: "삭제·동시성·금액 정합성 보호"
status: "pending"
success_data:
- "물리 삭제 경로 신규 추가 0건"
- "낙관적 잠금 실패가 명시적 오류 계약으로 매핑"
- "금액 계산의 부동소수점 암묵 변환 0건"
- id: "WBS-20260728-04"
title: "사후 검증·재현성·증빙 기록"
status: "pending"
success_data:
- "검증 명령과 결과가 Temp/ 증빙 파일에 존재"
- "변경 파일이 WBS 항목에 매핑됨"
constraints:
- "가격·수량·공식은 quant-engine spec을 재계산하지 않음"
- "Temp/ 산출물은 직접 편집하지 않음"
- "기존 사용자 변경을 덮어쓰지 않음"
+4
View File
@@ -1,3 +1,7 @@
> **ARCHIVED (2026-07-30)**: 본 문서의 CI/CD 로드맵은 2026-07-11 이후 구현 완료되었습니다.
> 현재 CI/CD 가이드는 [`docs/CICD_PIPELINE.md`](docs/CICD_PIPELINE.md) 및
> [`docs/DEPLOYMENT_RUNBOOK.md`](docs/DEPLOYMENT_RUNBOOK.md)을 참고하세요.
# QuantEngine Gitea Actions CI/CD 개선 로드맵 # QuantEngine Gitea Actions CI/CD 개선 로드맵
**최종 목표**: 신뢰성 높은 자동화된 배포 파이프라인 구축 **최종 목표**: 신뢰성 높은 자동화된 배포 파이프라인 구축
@@ -1,3 +1,7 @@
> **ARCHIVED (2026-07-30)**: 본 문서는 Phase 0-1 실행 계획(2026-07-24)으로, 현재 상태와 일부 차이가 있습니다.
> 최신 정보는 [`../CLAUDE.md`](../CLAUDE.md) Migration Status 섹션 또는
> [`STRATEGIC_EXECUTION_MASTER_PLAN.md`](../STRATEGIC_EXECUTION_MASTER_PLAN.md)를 참고하세요.
# QuantEngine 현대화 실행 계획 # QuantEngine 현대화 실행 계획
**Phase 0 마무리 + Phase 1 준비** (2026-07-24 ~ 2026-09-30) **Phase 0 마무리 + Phase 1 준비** (2026-07-24 ~ 2026-09-30)
@@ -1,3 +1,7 @@
> **ARCHIVED (2026-07-30)**: 본 문서의 Gantt 차트는 2026-07-24 기준 Phase 0-4 일정입니다.
> 최신 마이그레이션 상태는 [`../CLAUDE.md`](../CLAUDE.md)의 Migration Status 섹션 또는
> [`STRATEGIC_EXECUTION_MASTER_PLAN.md`](../STRATEGIC_EXECUTION_MASTER_PLAN.md)를 참고하세요.
# QuantEngine 현대화 로드맵 (시각화) # QuantEngine 현대화 로드맵 (시각화)
## 1. 전체 진행도 (Gantt Chart) ## 1. 전체 진행도 (Gantt Chart)
@@ -1,3 +1,7 @@
> **ARCHIVED (2026-07-30)**: 본 문서의 Phase 0-3 전략은 2026-07-24 기준이며, 최신 버전으로 대체되었습니다.
> 현재 마이그레이션 상태는 [`../CLAUDE.md`](../CLAUDE.md) Migration Status 또는
> [`STRATEGIC_EXECUTION_MASTER_PLAN.md`](../STRATEGIC_EXECUTION_MASTER_PLAN.md)를 참고하세요.
# QuantEngine 데이터 기반 고도화 로드맵 # QuantEngine 데이터 기반 고도화 로드맵
**2026-07-24 ~ 2027-06-30** **2026-07-24 ~ 2027-06-30**
@@ -0,0 +1,6 @@
namespace QuantEngine.Web.Pages.Shared;
public class DeleteConfirmModalViewModel
{
public string Message { get; set; } = string.Empty;
}
@@ -0,0 +1,10 @@
namespace QuantEngine.Web.Pages.Shared;
public class PageHeaderViewModel
{
public string Title { get; set; } = string.Empty;
public string PreTitle { get; set; } = string.Empty;
public bool HasAction { get; set; }
public string ActionText { get; set; } = string.Empty;
public string ActionUrl { get; set; } = string.Empty;
}
@@ -0,0 +1,34 @@
@model QuantEngine.Web.Pages.Shared.DeleteConfirmModalViewModel
<div class="modal modal-blur fade" id="modal-delete-confirm" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
<div class="modal-content">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="modal-status bg-danger"></div>
<form method="post" asp-page-handler="Delete">
@Html.AntiForgeryToken()
<input type="hidden" name="Id" id="delete-target-id" />
<div class="modal-body text-center py-4">
<i class="ti ti-alert-triangle text-danger fs-1 mb-2"></i>
<h3 class="fw-bold">정말 삭제하시겠습니까?</h3>
<div class="text-muted fs-7" id="delete-target-name">
@(string.IsNullOrEmpty(Model?.Message) ? "선택한 항목이 비활성(Soft Delete) 처리됩니다." : Model.Message)
</div>
</div>
<div class="modal-footer">
<div class="w-100">
<div class="row">
<div class="col">
<button type="button" class="btn w-100 btn-secondary" data-bs-dismiss="modal">취소</button>
</div>
<div class="col">
<button type="submit" class="btn w-100 btn-danger">삭제 실행</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
@@ -0,0 +1,21 @@
@model QuantEngine.Web.Pages.Shared.PageHeaderViewModel
<div class="page-header d-print-none mb-3">
<div class="row align-items-center">
<div class="col">
@if (!string.IsNullOrEmpty(Model.PreTitle))
{
<div class="page-pretitle text-muted text-uppercase fs-7 fw-bold mb-1">@Model.PreTitle</div>
}
<h2 class="page-title fw-bold">@Model.Title</h2>
</div>
@if (Model.HasAction)
{
<div class="col-auto ms-auto d-print-none">
<a href="@Model.ActionUrl" class="btn btn-primary">
@Model.ActionText
</a>
</div>
}
</div>
</div>
@@ -0,0 +1,377 @@
from __future__ import annotations
import math
from datetime import date, timedelta
from typing import Any
# 매도 결정에 동원하는 5개 독립 팩터군. 단일 팩터의 임계값 돌파만으로는 행동을
# 트리거하지 않는다 — 최소 CONFLUENCE_MIN개 팩터군이 동일 방향으로 합의해야
# SELL/ADD 확신도가 성립한다. (기계적 단일 트리거 매도 금지 원칙)
FACTOR_FAMILIES: tuple[str, ...] = (
"macro_pressure",
"fundamental_trajectory",
"short_interest_pressure",
"microstructure_pressure",
"liquidity_rotation_risk",
)
CONFLUENCE_MIN = 3
EVENT_PRE_GUARD_DAYS = 5 # macro_event_synchronizer_v2.event_hold_gate와 동일 — HIGH 이벤트 5일 전
EVENT_POST_GUARD_DAYS = 2 # 이벤트 후 2일 변동성 소화 구간
# 금리국면별 시장 성격: 금리 상승기=실적장세(펀더멘털/수출입 실적이 가격을 주도),
# 금리 보합·하락기=기술장세(수급·미시구조가 가격을 주도). 동일한 5팩터라도
# 국면에 따라 가중치를 달리 줘야 confluence가 의미를 갖는다.
REGIME_FLAT_WEIGHTS: dict[str, float] = {family: 1.0 for family in FACTOR_FAMILIES}
REGIME_WEIGHT_TABLE: dict[str, dict[str, float]] = {
"PERFORMANCE_MARKET": { # 금리 상승기 — 실적/수출입 펀더멘털 가중 상향
"macro_pressure": 1.2,
"fundamental_trajectory": 1.8,
"short_interest_pressure": 1.0,
"microstructure_pressure": 0.5,
"liquidity_rotation_risk": 1.0,
},
"TECHNICAL_MARKET": { # 금리 보합·하락기 — 수급/미시구조 가중 상향
"macro_pressure": 0.8,
"fundamental_trajectory": 0.8,
"short_interest_pressure": 1.3,
"microstructure_pressure": 1.6,
"liquidity_rotation_risk": 1.3,
},
"NEUTRAL": REGIME_FLAT_WEIGHTS,
}
def classify_market_regime(rate_trend: str | None) -> str:
"""금리 추세 문자열(RISING/FLAT/FALLING)을 실적장세/기술장세로 분류.
RISING → PERFORMANCE_MARKET(실적장세): 금리 상승기엔 유동성보다 실적/펀더멘털이
가격을 결정. FLAT/FALLING → TECHNICAL_MARKET(기술장세): 유동성이 풍부해 수급·
미시구조·테마성 모멘텀이 가격을 주도. 입력 결측 시 NEUTRAL(가중치 변화 없음).
"""
trend = str(rate_trend or "").upper()
if trend == "RISING":
return "PERFORMANCE_MARKET"
if trend in {"FLAT", "FALLING"}:
return "TECHNICAL_MARKET"
return "NEUTRAL"
def _finite(value: Any) -> bool:
return isinstance(value, (int, float)) and math.isfinite(float(value))
def compute_short_interest_composite(ctx: dict[str, Any]) -> dict[str, Any]:
"""SHORT_INTEREST_RISK_GAUGE_V1.
5요소: 공매도잔고율 변화, 공매도거래비중, 상대수익률(섹터/지수 대비),
거래량 이상, 실적전망. 잔고율 단독으로는 매도 근거가 약함(현대로템형) —
잔고율이 낮을 때는 거래비중·상대수익률 가중치를 자동 상향한다.
"""
missing: list[str] = []
short_balance_ratio = ctx.get("short_balance_ratio") # %, 현재 잔고율
short_balance_ratio_chg_20d = ctx.get("short_balance_ratio_chg_20d") # %p, 20일 변화
short_turnover_share = ctx.get("short_turnover_share") # 당일 거래 중 공매도 비중 %
relative_return_20d = ctx.get("relative_return_20d") # 종목수익률 - 섹터(or지수)수익률, %p
volume_ratio_5d = ctx.get("volume_ratio_5d") # 5일평균거래량 대비 비율
earnings_outlook = str(ctx.get("earnings_outlook") or "").upper() # IMPROVING|STABLE|DETERIORATING|UNKNOWN
for name, value in (
("short_balance_ratio", short_balance_ratio),
("short_turnover_share", short_turnover_share),
("relative_return_20d", relative_return_20d),
):
if not _finite(value):
missing.append(name)
if missing:
return {
"short_interest_pressure": None,
"status": "DATA_MISSING",
"missing_inputs": missing,
"note": "잔고율/거래비중/상대수익률 중 결측 — 공매도 합성 점수를 산출하지 않음(추정 금지)",
}
low_balance_regime = float(short_balance_ratio) < 1.0 # 잔고율 1% 미만이면 '낮은 잔고율' 취급(현대로템형)
# 잔고율 추세: 상승=매도근거 강화, 하락=매도근거 약화(혹은 매수근거)
balance_trend_signal = 0.0
if _finite(short_balance_ratio_chg_20d):
balance_trend_signal = max(-1.0, min(1.0, float(short_balance_ratio_chg_20d) / 1.5))
turnover_signal = max(-1.0, min(1.0, (float(short_turnover_share) - 8.0) / 12.0)) # 8% 기준선
relative_return_signal = max(-1.0, min(1.0, -float(relative_return_20d) / 10.0)) # 상대 약세일수록 +
volume_signal = 0.0
if _finite(volume_ratio_5d):
volume_signal = max(-1.0, min(1.0, (float(volume_ratio_5d) - 1.0)))
outlook_signal = {
"IMPROVING": -0.6,
"STABLE": 0.0,
"DETERIORATING": 0.7,
}.get(earnings_outlook, 0.0)
if low_balance_regime:
# 잔고율 자체는 약한 근거 — 거래비중·상대수익률 가중치 상향, 잔고율추세 가중치 하향
weights = {"balance": 0.10, "turnover": 0.30, "relative": 0.30, "volume": 0.10, "outlook": 0.20}
else:
weights = {"balance": 0.30, "turnover": 0.20, "relative": 0.20, "volume": 0.10, "outlook": 0.20}
pressure = (
balance_trend_signal * weights["balance"]
+ turnover_signal * weights["turnover"]
+ relative_return_signal * weights["relative"]
+ volume_signal * weights["volume"]
+ outlook_signal * weights["outlook"]
)
pressure = max(-1.0, min(1.0, pressure))
label = "ELEVATED_SHORT_PRESSURE" if pressure >= 0.5 else "WATCH" if pressure >= 0.2 else \
"SHORT_COVERING_SUPPORTIVE" if pressure <= -0.5 else "NEUTRAL"
return {
"short_interest_pressure": round(pressure, 4),
"status": "OK",
"low_balance_regime": low_balance_regime,
"label": label,
"components": {
"balance_trend_signal": round(balance_trend_signal, 4),
"turnover_signal": round(turnover_signal, 4),
"relative_return_signal": round(relative_return_signal, 4),
"volume_signal": round(volume_signal, 4),
"outlook_signal": outlook_signal,
},
"weights_used": weights,
}
def compute_microstructure_pressure_from_orderbook(orderbook_output1: dict[str, Any]) -> dict[str, Any]:
"""MICROSTRUCTURE_PRESSURE_FROM_ORDERBOOK_V1.
KIS Open API FHKST01010200(주식현재가 호가/예상체결) output1의 10단계 호가 잔량을
-1(매수우위/지지)~+1(매도우위/압력)로 계량화. 실측 확인된 필드명(2026-06-21,
005930 라이브 호출): total_askp_rsqn, total_bidp_rsqn(10단계 합계 잔량).
이 점수는 전략 방향 결정에는 쓰지 않고 confluence가 성립한 이후의 '집행 타이밍'
보조로만 사용한다(spec/exit/qualitative_sell_strategy_v1.yaml:factor_families.
microstructure_pressure 참조).
"""
total_askp = orderbook_output1.get("total_askp_rsqn")
total_bidp = orderbook_output1.get("total_bidp_rsqn")
try:
total_askp = float(total_askp)
total_bidp = float(total_bidp)
except (TypeError, ValueError):
return {"microstructure_pressure": None, "status": "DATA_MISSING"}
denom = total_askp + total_bidp
if denom <= 0:
return {"microstructure_pressure": None, "status": "DATA_MISSING"}
pressure = max(-1.0, min(1.0, (total_askp - total_bidp) / denom))
return {
"microstructure_pressure": round(pressure, 4),
"status": "OK",
"total_askp_rsqn": total_askp,
"total_bidp_rsqn": total_bidp,
}
def _event_review_window(
today: date,
pressure_sign: int,
next_earnings_date: date | None,
next_macro_event_date: date | None,
macro_event_impact: str | None,
earnings_outlook: str,
) -> dict[str, Any]:
"""캘린더 기반 검토 구간 산출 — 임의 날짜 고정이 아니라 실제 이벤트 일정에서 역산."""
candidates: list[tuple[date, str]] = []
if next_earnings_date is not None:
if pressure_sign < 0 and earnings_outlook == "DETERIORATING":
# 실적 악화 전망 + 매도압력 → 실적발표 전 정리(서프라이즈 리스크 회피)
candidates.append((next_earnings_date - timedelta(days=EVENT_PRE_GUARD_DAYS), "PRE_EARNINGS_EXIT_BEFORE_SURPRISE_RISK"))
elif pressure_sign < 0 and earnings_outlook in {"IMPROVING", "STABLE"}:
# 단기 기술적 매도압력이지만 실적전망은 양호 → 발표 직전 매도는 가치훼손, 발표 이후로 연기
candidates.append((next_earnings_date + timedelta(days=EVENT_POST_GUARD_DAYS), "DEFER_TO_POST_EARNINGS_AVOID_PREMATURE_EXIT"))
elif pressure_sign > 0:
# 추가매수/보유 신호 — 발표 변동성 통과 후 확신 재평가
candidates.append((next_earnings_date + timedelta(days=EVENT_POST_GUARD_DAYS), "REASSESS_AFTER_EARNINGS_CONFIRM"))
if next_macro_event_date is not None and str(macro_event_impact or "").upper() in {"HIGH", "VERY_HIGH"}:
if pressure_sign < 0:
candidates.append((next_macro_event_date - timedelta(days=EVENT_PRE_GUARD_DAYS), "PRE_MACRO_EVENT_DERISK"))
else:
candidates.append((next_macro_event_date + timedelta(days=EVENT_POST_GUARD_DAYS), "POST_MACRO_EVENT_CONFIRM"))
if not candidates:
return {
"review_window_start": today.isoformat(),
"review_window_end": (today + timedelta(days=10)).isoformat(),
"window_basis": "NO_SCHEDULED_EVENT_DEFAULT_10D_REVIEW",
}
earliest = min(candidates, key=lambda item: item[0])
window_start = max(today, earliest[0] - timedelta(days=2))
window_end = earliest[0] + timedelta(days=2)
return {
"review_window_start": window_start.isoformat(),
"review_window_end": window_end.isoformat(),
"window_basis": earliest[1],
}
def compute_qualitative_sell_strategy(ctx: dict[str, Any]) -> dict[str, Any]:
"""QUALITATIVE_SELL_STRATEGY_V1.
매크로/실적/펀더멘털/공매도수급/호가미시구조/대내외(IPO·로테이션) 5개
독립 팩터군의 합의(confluence)로만 행동을 생성한다. 현금부족 사유는
입력에서 의도적으로 배제(cash_shortfall_excluded=True) — 가치보존이
유일한 목적 함수.
"""
today_raw = ctx.get("today")
today = today_raw if isinstance(today_raw, date) else date.today()
factor_values: dict[str, float | None] = {}
missing_factors: list[str] = []
for family in FACTOR_FAMILIES:
value = ctx.get(family)
if _finite(value):
factor_values[family] = max(-1.0, min(1.0, float(value)))
else:
factor_values[family] = None
missing_factors.append(family)
available = {k: v for k, v in factor_values.items() if v is not None}
if len(available) < CONFLUENCE_MIN:
return {
"action": "INSUFFICIENT_DATA_NO_ACTION",
"conviction": "NONE",
"available_factors": list(available.keys()),
"missing_factors": missing_factors,
"rationale": "5개 팩터군 중 confluence 판정에 필요한 최소 데이터가 부족 — 추정으로 행동 생성 금지",
"cash_shortfall_excluded": True,
"mechanical_sell_prohibited": True,
}
# 부호 규약: 모든 팩터군은 +1(매도압력 최대) ~ -1(보유/추가 지지 최대) 동일 스케일.
# short_interest_pressure도 동일 — ELEVATED_SHORT_PRESSURE(+) / SHORT_COVERING_SUPPORTIVE(-).
# confluence 합의 카운트는 국면 가중치와 무관하게 원시 방향성으로만 판정한다
# (가중치는 행동 '강도'에만 영향 — 합의 성립 여부 자체를 왜곡하지 않는다).
sell_agree = [k for k, v in available.items() if v >= 0.30]
hold_add_agree = [k for k, v in available.items() if v <= -0.30]
market_regime = classify_market_regime(ctx.get("rate_trend")) if "market_regime" not in ctx else str(ctx.get("market_regime") or "NEUTRAL").upper()
regime_weights = REGIME_WEIGHT_TABLE.get(market_regime, REGIME_FLAT_WEIGHTS)
weighted_sum = sum(available[k] * regime_weights.get(k, 1.0) for k in available)
weight_total = sum(regime_weights.get(k, 1.0) for k in available)
composite_score = weighted_sum / weight_total if weight_total else 0.0
earnings_outlook = str(ctx.get("earnings_outlook") or "STABLE").upper()
next_earnings_date = ctx.get("next_earnings_date") if isinstance(ctx.get("next_earnings_date"), date) else None
next_macro_event_date = ctx.get("next_macro_event_date") if isinstance(ctx.get("next_macro_event_date"), date) else None
macro_event_impact = ctx.get("macro_event_impact")
if len(sell_agree) >= CONFLUENCE_MIN:
conviction = "HIGH" if len(sell_agree) >= 4 else "MEDIUM"
action = "EXIT_REVIEW_FULL" if composite_score >= 0.6 else "TRIM_REVIEW_PARTIAL"
pressure_sign = -1
rationale = f"매도압력 합의({len(sell_agree)}/{len(available)} 팩터군 매도방향 합치): " + ", ".join(sell_agree)
elif len(hold_add_agree) >= CONFLUENCE_MIN:
conviction = "HIGH" if len(hold_add_agree) >= 4 else "MEDIUM"
action = "HOLD_ADD_CONVICTION"
pressure_sign = 1
rationale = f"보유/추가 근거 합의({len(hold_add_agree)}/{len(available)} 팩터군 지지방향 합치): " + ", ".join(hold_add_agree)
else:
conviction = "LOW"
action = "HOLD_NO_CONFLUENCE"
pressure_sign = 0
rationale = "팩터군 간 합의 미달 — 단일/소수 팩터의 임계값 돌파만으로는 매도 트리거 금지"
window = _event_review_window(
today=today,
pressure_sign=pressure_sign,
next_earnings_date=next_earnings_date,
next_macro_event_date=next_macro_event_date,
macro_event_impact=macro_event_impact,
earnings_outlook=earnings_outlook,
) if pressure_sign != 0 else None
return {
"action": action,
"conviction": conviction,
"market_regime": market_regime,
"composite_score": round(composite_score, 4),
"sell_agreeing_factors": sell_agree,
"hold_add_agreeing_factors": hold_add_agree,
"missing_factors": missing_factors,
"review_window": window,
"rationale": rationale,
"cash_shortfall_excluded": True,
"mechanical_sell_prohibited": True,
}
def compute_satellite_candidate_score(ctx: dict[str, Any]) -> dict[str, Any]:
"""SATELLITE_CANDIDATE_SCORE_V1.
미보유 유니버스 종목을 섹터 수출입 전망(sector_export_trend) + 펀더멘털
추세 + 국면적합도로 평가해 WATCH/BUY_CANDIDATE/AVOID를 산출한다. 보유종목
매도판단(compute_qualitative_sell_strategy)과 동일한 부호 규약을 쓰지 않고
별도 -1(약세)~+1(강세) 매력도 스케일을 쓴다 — 매수후보 평가와 매도판단은
목적함수가 다르므로 동일 점수를 재사용하지 않는다.
"""
sector_export_trend = ctx.get("sector_export_trend") # %, 섹터 수출 YoY/MoM 추세
fundamental_trajectory = ctx.get("fundamental_trajectory") # -1(악화)~+1(개선), 매도엔진과 동일 정의역이나 부호 반대 해석 주의
relative_return_20d = ctx.get("relative_return_20d")
market_regime = str(ctx.get("market_regime") or classify_market_regime(ctx.get("rate_trend"))).upper()
missing = [name for name, value in (
("sector_export_trend", sector_export_trend),
("fundamental_trajectory", fundamental_trajectory),
) if not _finite(value)]
if missing:
return {
"satellite_action": "INSUFFICIENT_DATA_NO_ACTION",
"missing_inputs": missing,
"market_regime": market_regime,
}
export_signal = max(-1.0, min(1.0, float(sector_export_trend) / 10.0))
fundamental_signal = max(-1.0, min(1.0, -float(fundamental_trajectory))) # 매도엔진 부호(+)=악화 -> 매력도는 반전
relative_signal = max(-1.0, min(1.0, float(relative_return_20d) / 10.0)) if _finite(relative_return_20d) else 0.0
if market_regime == "PERFORMANCE_MARKET":
weights = {"export": 0.45, "fundamental": 0.40, "relative": 0.15}
elif market_regime == "TECHNICAL_MARKET":
weights = {"export": 0.20, "fundamental": 0.25, "relative": 0.55}
else:
weights = {"export": 0.34, "fundamental": 0.33, "relative": 0.33}
attractiveness = (
export_signal * weights["export"]
+ fundamental_signal * weights["fundamental"]
+ relative_signal * weights["relative"]
)
attractiveness = max(-1.0, min(1.0, attractiveness))
if attractiveness >= 0.5:
satellite_action = "BUY_CANDIDATE"
elif attractiveness >= 0.2:
satellite_action = "WATCH"
elif attractiveness <= -0.4:
satellite_action = "AVOID"
else:
satellite_action = "NEUTRAL_NO_EDGE"
return {
"satellite_action": satellite_action,
"attractiveness_score": round(attractiveness, 4),
"market_regime": market_regime,
"components": {
"export_signal": round(export_signal, 4),
"fundamental_signal": round(fundamental_signal, 4),
"relative_signal": round(relative_signal, 4),
},
"weights_used": weights,
}
@@ -1,126 +0,0 @@
from __future__ import annotations
import ast
import json
import re
from dataclasses import dataclass
from pathlib import Path
from src.quant_engine.refactor_master_helpers import ROOT, collect_gas_files
FORBIDDEN_TOKENS = ("decision", "sizing", "stop_loss", "take_profit", "risk_score")
ALLOWED_TOKENS = ("collect", "normalize", "export", "display")
_JS_BLOCK_COMMENT_RE = re.compile(r"/\*.*?\*/", re.S)
_JS_LINE_COMMENT_RE = re.compile(r"//.*?$", re.M)
_JS_STRING_RE = re.compile(
r"""(?x)
(?:
"(?:\\.|[^"\\])*"
| '(?:\\.|[^'\\])*'
| `(?:\\.|[^`\\])*`
)
"""
)
@dataclass
class FunctionRow:
file: str
name: str
line: int
allowed_responsibility: str
matched_tokens: list[str]
def classify(name: str, body: str, file_name: str) -> tuple[str, list[str]]:
cleaned = _JS_BLOCK_COMMENT_RE.sub(" ", body)
cleaned = _JS_LINE_COMMENT_RE.sub(" ", cleaned)
cleaned = _JS_STRING_RE.sub(" ", cleaned)
low = f"{name}\n{cleaned}".lower()
matched_forbidden = [
tok
for tok in FORBIDDEN_TOKENS
if re.search(rf"(?<![A-Za-z0-9_]){re.escape(tok)}(?![A-Za-z0-9_])", low)
]
matched_allowed = [
tok
for tok in ALLOWED_TOKENS
if re.search(rf"(?<![A-Za-z0-9_]){re.escape(tok)}(?![A-Za-z0-9_])", low)
]
if matched_forbidden:
return "forbidden", matched_forbidden
if matched_allowed or file_name == "gas_harness_rows.gs":
return "allowed", matched_allowed or ["harness_rows"]
return "helper", []
def function_bodies(path: Path) -> list[tuple[str, int, str]]:
text = path.read_text(encoding="utf-8", errors="ignore")
lines = text.splitlines()
header_indexes: list[tuple[str, int]] = []
header_re = re.compile(r"^(?:function\s+([A-Za-z0-9_]+)|(?:var|let|const)\s+([A-Za-z0-9_]+)\s*=\s*function\b)")
for idx, line in enumerate(lines):
stripped = line.strip()
m = header_re.match(stripped)
if m:
name = (m.group(1) or m.group(2) or "").strip()
header_indexes.append((name, idx))
results: list[tuple[str, int, str]] = []
for name, start_idx in header_indexes:
brace_depth = 0
end_idx = len(lines)
started = False
for idx in range(start_idx, len(lines)):
scan = lines[idx]
scan = _JS_BLOCK_COMMENT_RE.sub(" ", scan)
scan = _JS_LINE_COMMENT_RE.sub(" ", scan)
scan = _JS_STRING_RE.sub(" ", scan)
brace_depth += scan.count("{")
if scan.count("{"):
started = True
brace_depth -= scan.count("}")
if started and brace_depth <= 0:
end_idx = idx + 1
break
body = "\n".join(lines[start_idx:end_idx])
results.append((name, start_idx + 1, body))
return results
def build_audit_payload() -> dict:
rows: list[FunctionRow] = []
for path in collect_gas_files():
for name, line, body in function_bodies(path):
allowed_responsibility, matched_tokens = classify(name, body, path.name)
rows.append(
FunctionRow(
file=str(path.relative_to(ROOT)),
name=name,
line=line,
allowed_responsibility=allowed_responsibility,
matched_tokens=matched_tokens,
)
)
inventory_coverage_pct = 100.0 if rows else 0.0
forbidden_rows = [row for row in rows if row.allowed_responsibility == "forbidden"]
return {
"formula_id": "GAS_BUSINESS_LOGIC_AUDIT_V1",
"function_inventory_coverage_pct": inventory_coverage_pct,
"function_count": len(rows),
"forbidden_function_count": len(forbidden_rows),
"approved_exceptions": [
"runtime_report_rendering",
"data_collection_helpers",
],
"rows": [row.__dict__ for row in rows],
"gate": "PASS" if inventory_coverage_pct >= 100.0 else "FAIL",
}
def write_audit(out_path: Path) -> dict:
result = build_audit_payload()
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
return result
-1
View File
@@ -1 +0,0 @@
"""Auto-generated package."""
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ABSOLUTE_RISK_STOP_V1."""
def test_absolute_risk_stop_v1_golden_stub_exists() -> None:
assert 'ABSOLUTE_RISK_STOP_V1'
def test_absolute_risk_stop_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ALGORITHM_GUIDANCE_PROOF_V1."""
def test_algorithm_guidance_proof_v1_golden_stub_exists() -> None:
assert 'ALGORITHM_GUIDANCE_PROOF_V1'
def test_algorithm_guidance_proof_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ALPHA_EVALUATION_WINDOW_V1."""
def test_alpha_evaluation_window_v1_golden_stub_exists() -> None:
assert 'ALPHA_EVALUATION_WINDOW_V1'
def test_alpha_evaluation_window_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ALPHA_FEEDBACK_LOOP_V1."""
def test_alpha_feedback_loop_v1_golden_stub_exists() -> None:
assert 'ALPHA_FEEDBACK_LOOP_V1'
def test_alpha_feedback_loop_v1_declares_outputs() -> None:
outputs = [{'field': 'alpha_feedback_json', 'subfields': ['eligible_t20_fail_rate', 'eligible_t60_fail_rate', 'recommended_filter_adjustments', 'cases_analyzed']}]
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ANTI_CHASE_V1."""
def test_anti_chase_v1_golden_stub_exists() -> None:
assert 'ANTI_CHASE_V1'
def test_anti_chase_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ANTI_CHASING_VELOCITY_V1."""
def test_anti_chasing_velocity_v1_golden_stub_exists() -> None:
assert 'ANTI_CHASING_VELOCITY_V1'
def test_anti_chasing_velocity_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ANTI_LATE_ENTRY_GATE_V2."""
def test_anti_late_entry_gate_v2_golden_stub_exists() -> None:
assert 'ANTI_LATE_ENTRY_GATE_V2'
def test_anti_late_entry_gate_v2_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ANTI_WHIPSAW_GATE_V1."""
def test_anti_whipsaw_gate_v1_golden_stub_exists() -> None:
assert 'ANTI_WHIPSAW_GATE_V1'
def test_anti_whipsaw_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ARTIFACT_FRESHNESS_GATE_V1."""
def test_artifact_freshness_gate_v1_golden_stub_exists() -> None:
assert 'ARTIFACT_FRESHNESS_GATE_V1'
def test_artifact_freshness_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for AUDIT_REPLAY_SNAPSHOT_V1."""
def test_audit_replay_snapshot_v1_golden_stub_exists() -> None:
assert 'AUDIT_REPLAY_SNAPSHOT_V1'
def test_audit_replay_snapshot_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for BENCHMARK_RELATIVE_TIMESERIES_V1."""
def test_benchmark_relative_timeseries_v1_golden_stub_exists() -> None:
assert 'BENCHMARK_RELATIVE_TIMESERIES_V1'
def test_benchmark_relative_timeseries_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for BLANK_CELL_AUDIT_V1."""
def test_blank_cell_audit_v1_golden_stub_exists() -> None:
assert 'BLANK_CELL_AUDIT_V1'
def test_blank_cell_audit_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for BREAKEVEN_RATCHET_V1."""
def test_breakeven_ratchet_v1_golden_stub_exists() -> None:
assert 'BREAKEVEN_RATCHET_V1'
def test_breakeven_ratchet_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for BREAKOUT_FAILURE_STOP_V1."""
def test_breakout_failure_stop_v1_golden_stub_exists() -> None:
assert 'BREAKOUT_FAILURE_STOP_V1'
def test_breakout_failure_stop_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for BREAKOUT_QUALITY_GATE_V2."""
def test_breakout_quality_gate_v2_golden_stub_exists() -> None:
assert 'BREAKOUT_QUALITY_GATE_V2'
def test_breakout_quality_gate_v2_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CANONICAL_ARTIFACT_RESOLVER_V1."""
def test_canonical_artifact_resolver_v1_golden_stub_exists() -> None:
assert 'CANONICAL_ARTIFACT_RESOLVER_V1'
def test_canonical_artifact_resolver_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CANONICAL_METRICS_V1."""
def test_canonical_metrics_v1_golden_stub_exists() -> None:
assert 'CANONICAL_METRICS_V1'
def test_canonical_metrics_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CAPITAL_STYLE_ALLOCATION_V1."""
def test_capital_style_allocation_v1_golden_stub_exists() -> None:
assert 'CAPITAL_STYLE_ALLOCATION_V1'
def test_capital_style_allocation_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_CREATION_PURPOSE_LOCK_V1."""
def test_cash_creation_purpose_lock_v1_golden_stub_exists() -> None:
assert 'CASH_CREATION_PURPOSE_LOCK_V1'
def test_cash_creation_purpose_lock_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_FLOOR_V1."""
def test_cash_floor_v1_golden_stub_exists() -> None:
assert 'CASH_FLOOR_V1'
def test_cash_floor_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_RAISE_PARETO_EXECUTOR_V2."""
def test_cash_raise_pareto_executor_v2_golden_stub_exists() -> None:
assert 'CASH_RAISE_PARETO_EXECUTOR_V2'
def test_cash_raise_pareto_executor_v2_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_RAISE_VALUE_OPTIMIZER_V3."""
def test_cash_raise_value_optimizer_v3_golden_stub_exists() -> None:
assert 'CASH_RAISE_VALUE_OPTIMIZER_V3'
def test_cash_raise_value_optimizer_v3_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_RATIOS_V1."""
def test_cash_ratios_v1_golden_stub_exists() -> None:
assert 'CASH_RATIOS_V1'
def test_cash_ratios_v1_declares_outputs() -> None:
outputs = {'settlement_cash_ratio': 'settlement_cash / total_asset * 100', 'total_cash_ratio': 'settlement_cash / total_asset * 100', 'buy_power_cash': 'settlement_cash - reserved_order_amount', 'buy_power_ratio': '(settlement_cash - reserved_order_amount) / total_asset * 100', 'post_trade_total_cash_ratio': '(settlement_cash - planned_buy_amount + sell_cash_proceeds_d2) / total_asset * 100'}
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_RECOVERY_OPTIMIZER_V1."""
def test_cash_recovery_optimizer_v1_golden_stub_exists() -> None:
assert 'CASH_RECOVERY_OPTIMIZER_V1'
def test_cash_recovery_optimizer_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_RECOVERY_OPTIMIZER_V4."""
def test_cash_recovery_optimizer_v4_golden_stub_exists() -> None:
assert 'CASH_RECOVERY_OPTIMIZER_V4'
def test_cash_recovery_optimizer_v4_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASH_RECOVERY_V1."""
def test_cash_recovery_v1_golden_stub_exists() -> None:
assert 'CASH_RECOVERY_V1'
def test_cash_recovery_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASHFLOW_QUALITY_SIGNAL_V1."""
def test_cashflow_quality_signal_v1_golden_stub_exists() -> None:
assert 'CASHFLOW_QUALITY_SIGNAL_V1'
def test_cashflow_quality_signal_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CASHFLOW_STABILITY_GATE_V1."""
def test_cashflow_stability_gate_v1_golden_stub_exists() -> None:
assert 'CASHFLOW_STABILITY_GATE_V1'
def test_cashflow_stability_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CLA_REGIME_EXIT_CONDITION_V1."""
def test_cla_regime_exit_condition_v1_golden_stub_exists() -> None:
assert 'CLA_REGIME_EXIT_CONDITION_V1'
def test_cla_regime_exit_condition_v1_declares_outputs() -> None:
outputs = [{'field': 'cla_exit_status', 'unit': 'enum [CLA_ACTIVE,CLA_EXIT_WARNING,CLA_EXIT_CONFIRMED]'}, {'field': 'cla_exit_signals_triggered', 'unit': 'list'}, {'field': 'cla_exit_total_weight', 'unit': 'int'}]
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for COMPLETION_GAP_V1."""
def test_completion_gap_v1_golden_stub_exists() -> None:
assert 'COMPLETION_GAP_V1'
def test_completion_gap_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for COMPOSITE_VERDICT_V1."""
def test_composite_verdict_v1_golden_stub_exists() -> None:
assert 'COMPOSITE_VERDICT_V1'
def test_composite_verdict_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for COMPREHENSIVE_PROPOSAL_V1."""
def test_comprehensive_proposal_v1_golden_stub_exists() -> None:
assert 'COMPREHENSIVE_PROPOSAL_V1'
def test_comprehensive_proposal_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CONSECUTIVE_STREAK_V1."""
def test_consecutive_streak_v1_golden_stub_exists() -> None:
assert 'CONSECUTIVE_STREAK_V1'
def test_consecutive_streak_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CONTINUOUS_EVALUATION_DASHBOARD_V1."""
def test_continuous_evaluation_dashboard_v1_golden_stub_exists() -> None:
assert 'CONTINUOUS_EVALUATION_DASHBOARD_V1'
def test_continuous_evaluation_dashboard_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for CROSS_SECTION_CONSISTENCY_V1."""
def test_cross_section_consistency_v1_golden_stub_exists() -> None:
assert 'CROSS_SECTION_CONSISTENCY_V1'
def test_cross_section_consistency_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DATA_INTEGRITY_100_LOCK_V1."""
def test_data_integrity_100_lock_v1_golden_stub_exists() -> None:
assert 'DATA_INTEGRITY_100_LOCK_V1'
def test_data_integrity_100_lock_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DATA_INTEGRITY_100_LOCK_V2."""
def test_data_integrity_100_lock_v2_golden_stub_exists() -> None:
assert 'DATA_INTEGRITY_100_LOCK_V2'
def test_data_integrity_100_lock_v2_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DATA_INTEGRITY_SCORE_V1."""
def test_data_integrity_score_v1_golden_stub_exists() -> None:
assert 'DATA_INTEGRITY_SCORE_V1'
def test_data_integrity_score_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DATA_MATURITY_TRUTH_GATE_V1."""
def test_data_maturity_truth_gate_v1_golden_stub_exists() -> None:
assert 'DATA_MATURITY_TRUTH_GATE_V1'
def test_data_maturity_truth_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DATA_MATURITY_TRUTH_GATE_VALIDATOR_V1."""
def test_data_maturity_truth_gate_validator_v1_golden_stub_exists() -> None:
assert 'DATA_MATURITY_TRUTH_GATE_VALIDATOR_V1'
def test_data_maturity_truth_gate_validator_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DATA_QUALITY_GATE_V2_PY."""
def test_data_quality_gate_v2_py_golden_stub_exists() -> None:
assert 'DATA_QUALITY_GATE_V2_PY'
def test_data_quality_gate_v2_py_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DATA_QUALITY_GATE_V3."""
def test_data_quality_gate_v3_golden_stub_exists() -> None:
assert 'DATA_QUALITY_GATE_V3'
def test_data_quality_gate_v3_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DETERMINISTIC_ROUTING_ENGINE_V1."""
def test_deterministic_routing_engine_v1_golden_stub_exists() -> None:
assert 'DETERMINISTIC_ROUTING_ENGINE_V1'
def test_deterministic_routing_engine_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DISTRIBUTION_SELL_DETECTOR_V1."""
def test_distribution_sell_detector_v1_golden_stub_exists() -> None:
assert 'DISTRIBUTION_SELL_DETECTOR_V1'
def test_distribution_sell_detector_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DIVERGENCE_SCORE_V1."""
def test_divergence_score_v1_golden_stub_exists() -> None:
assert 'DIVERGENCE_SCORE_V1'
def test_divergence_score_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DRAWDOWN_GUARD_V1."""
def test_drawdown_guard_v1_golden_stub_exists() -> None:
assert 'DRAWDOWN_GUARD_V1'
def test_drawdown_guard_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for DYNAMIC_HEAT_GATE_V1."""
def test_dynamic_heat_gate_v1_golden_stub_exists() -> None:
assert 'DYNAMIC_HEAT_GATE_V1'
def test_dynamic_heat_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EARNINGS_GROWTH_QUALITY_GATE_V1."""
def test_earnings_growth_quality_gate_v1_golden_stub_exists() -> None:
assert 'EARNINGS_GROWTH_QUALITY_GATE_V1'
def test_earnings_growth_quality_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EARNINGS_QUALITY_SIGNAL_V1."""
def test_earnings_quality_signal_v1_golden_stub_exists() -> None:
assert 'EARNINGS_QUALITY_SIGNAL_V1'
def test_earnings_quality_signal_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ECP_RISK_SCALE_V1."""
def test_ecp_risk_scale_v1_golden_stub_exists() -> None:
assert 'ECP_RISK_SCALE_V1'
def test_ecp_risk_scale_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EJCE_DIVERGENCE_AUDIT_V1."""
def test_ejce_divergence_audit_v1_golden_stub_exists() -> None:
assert 'EJCE_DIVERGENCE_AUDIT_V1'
def test_ejce_divergence_audit_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EJCE_VIEW_RENDERER_V1."""
def test_ejce_view_renderer_v1_golden_stub_exists() -> None:
assert 'EJCE_VIEW_RENDERER_V1'
def test_ejce_view_renderer_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for ENTRY_TIMING_DECILE_FACTOR_V1."""
def test_entry_timing_decile_factor_v1_golden_stub_exists() -> None:
assert 'ENTRY_TIMING_DECILE_FACTOR_V1'
def test_entry_timing_decile_factor_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EXECUTION_CAPACITY_LADDER_V1."""
def test_execution_capacity_ladder_v1_golden_stub_exists() -> None:
assert 'EXECUTION_CAPACITY_LADDER_V1'
def test_execution_capacity_ladder_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EXECUTION_METHOD_LADDER_V1."""
def test_execution_method_ladder_v1_golden_stub_exists() -> None:
assert 'EXECUTION_METHOD_LADDER_V1'
def test_execution_method_ladder_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EXECUTION_PLAN_COMPILER_V1."""
def test_execution_plan_compiler_v1_golden_stub_exists() -> None:
assert 'EXECUTION_PLAN_COMPILER_V1'
def test_execution_plan_compiler_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EXECUTION_QUALITY_SCORE_V1."""
def test_execution_quality_score_v1_golden_stub_exists() -> None:
assert 'EXECUTION_QUALITY_SCORE_V1'
def test_execution_quality_score_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for EXPECTED_EDGE_V1."""
def test_expected_edge_v1_golden_stub_exists() -> None:
assert 'EXPECTED_EDGE_V1'
def test_expected_edge_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FIFTY_TWO_WEEK_HIGH_TRIGGER_V1."""
def test_fifty_two_week_high_trigger_v1_golden_stub_exists() -> None:
assert 'FIFTY_TWO_WEEK_HIGH_TRIGGER_V1'
def test_fifty_two_week_high_trigger_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FINAL_JUDGMENT_GATE_V1."""
def test_final_judgment_gate_v1_golden_stub_exists() -> None:
assert 'FINAL_JUDGMENT_GATE_V1'
def test_final_judgment_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FINANCIAL_HEALTH_SCORE_V1."""
def test_financial_health_score_v1_golden_stub_exists() -> None:
assert 'FINANCIAL_HEALTH_SCORE_V1'
def test_financial_health_score_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FLOW_ACCELERATION_V1."""
def test_flow_acceleration_v1_golden_stub_exists() -> None:
assert 'FLOW_ACCELERATION_V1'
def test_flow_acceleration_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FLOW_CREDIT_V1."""
def test_flow_credit_v1_golden_stub_exists() -> None:
assert 'FLOW_CREDIT_V1'
def test_flow_credit_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FOLLOW_THROUGH_DAY_CONFIRM_V1."""
def test_follow_through_day_confirm_v1_golden_stub_exists() -> None:
assert 'FOLLOW_THROUGH_DAY_CONFIRM_V1'
def test_follow_through_day_confirm_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FORECAST_SIMULATION_ENGINE_V1."""
def test_forecast_simulation_engine_v1_golden_stub_exists() -> None:
assert 'FORECAST_SIMULATION_ENGINE_V1'
def test_forecast_simulation_engine_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FUNDAMENTAL_MULTI_FACTOR_SCORE_V2."""
def test_fundamental_multi_factor_score_v2_golden_stub_exists() -> None:
assert 'FUNDAMENTAL_MULTI_FACTOR_SCORE_V2'
def test_fundamental_multi_factor_score_v2_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FUNDAMENTAL_MULTIFACTOR_V3."""
def test_fundamental_multifactor_v3_golden_stub_exists() -> None:
assert 'FUNDAMENTAL_MULTIFACTOR_V3'
def test_fundamental_multifactor_v3_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FUNDAMENTAL_QUALITY_GATE_V1."""
def test_fundamental_quality_gate_v1_golden_stub_exists() -> None:
assert 'FUNDAMENTAL_QUALITY_GATE_V1'
def test_fundamental_quality_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for FUNDAMENTAL_RAW_INGEST_V1."""
def test_fundamental_raw_ingest_v1_golden_stub_exists() -> None:
assert 'FUNDAMENTAL_RAW_INGEST_V1'
def test_fundamental_raw_ingest_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for GOLDEN_CROSS_SIGNAL_V1."""
def test_golden_cross_signal_v1_golden_stub_exists() -> None:
assert 'GOLDEN_CROSS_SIGNAL_V1'
def test_golden_cross_signal_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for GROWTH_RATE_SIGNAL_V1."""
def test_growth_rate_signal_v1_golden_stub_exists() -> None:
assert 'GROWTH_RATE_SIGNAL_V1'
def test_growth_rate_signal_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for HARNESS_DATA_FRESHNESS_GATE_V1."""
def test_harness_data_freshness_gate_v1_golden_stub_exists() -> None:
assert 'HARNESS_DATA_FRESHNESS_GATE_V1'
def test_harness_data_freshness_gate_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for HEAT_CONCENTRATION_ALERT_V1."""
def test_heat_concentration_alert_v1_golden_stub_exists() -> None:
assert 'HEAT_CONCENTRATION_ALERT_V1'
def test_heat_concentration_alert_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for HORIZON_ALLOCATION_LOCK_V1."""
def test_horizon_allocation_lock_v1_golden_stub_exists() -> None:
assert 'HORIZON_ALLOCATION_LOCK_V1'
def test_horizon_allocation_lock_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for HORIZON_CLASSIFICATION_V1."""
def test_horizon_classification_v1_golden_stub_exists() -> None:
assert 'HORIZON_CLASSIFICATION_V1'
def test_horizon_classification_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for IMMUTABLE_DECISION_LEDGER_V1."""
def test_immutable_decision_ledger_v1_golden_stub_exists() -> None:
assert 'IMMUTABLE_DECISION_LEDGER_V1'
def test_immutable_decision_ledger_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for INTRADAY_ACTION_MATRIX_V1."""
def test_intraday_action_matrix_v1_golden_stub_exists() -> None:
assert 'INTRADAY_ACTION_MATRIX_V1'
def test_intraday_action_matrix_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs
@@ -1,9 +0,0 @@
"""Auto-generated golden test stub for INVESTMENT_QUALITY_HEADLINE_V1."""
def test_investment_quality_headline_v1_golden_stub_exists() -> None:
assert 'INVESTMENT_QUALITY_HEADLINE_V1'
def test_investment_quality_headline_v1_declares_outputs() -> None:
outputs = []
assert isinstance(outputs, list)
assert outputs

Some files were not shown because too many files have changed in this diff Show More