Compare commits
11 Commits
88e3d58664
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 11e8a3c7f8 | |||
| 65e3769eb2 | |||
| 4dd6098619 | |||
| 70e49f0556 | |||
| 73572b6211 | |||
| 2439d5e24d | |||
| 977167a0db | |||
| fa0da0b159 | |||
| 823a9a64e2 | |||
| 0a2f0eb4a1 | |||
| 8dabffc08a |
@@ -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
|
||||
@@ -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).
|
||||
- **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 전략 감사 세션 인수인계**: 미해결 항목(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
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|---|---|
|
||||
| [`AGENTS.md`](../AGENTS.md) | 운영 헌법, Directory Routing 인덱스 |
|
||||
| [`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 토큰 관리 런북 |
|
||||
| [`spec/00_execution_contract.yaml`](../spec/00_execution_contract.yaml) | 실행 계약 원본 권위 |
|
||||
| [`governance/agents_index.yaml`](../governance/agents_index.yaml) | 거버넌스 규칙 인덱스 |
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
1. `GatherTradingData.json`이 있으면 그 파일을 우선 사용한다.
|
||||
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`를 확인한다.
|
||||
5. KIS 토큰은 `src/quant_engine/kis_api_client_v1.py`가 SQLite 캐시로 관리하므로, 수집 재실행 시에도 토큰을 매번 새로 발급하지 않는다.
|
||||
6. 토큰 상태는 `python tools/inspect_kis_token_cache_v1.py`로 확인한다.
|
||||
|
||||
@@ -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 파이프라인을 돌려야 한다.
|
||||
|
||||
+9
@@ -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)
|
||||
|
||||
## 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 & 하네스 성공 기준
|
||||
|
||||
> 작성일: 2026-06-13 | 엔진 버전: REBALANCE_ENGINE_V1 기준
|
||||
@@ -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)
|
||||
|
||||
> 본 문서는 [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 소스 실측 결과 **표기와 실제 상태 간 괴리**가 확인되었다. 본 문서는 그 괴리를 정리하고 실제 잔여 작업을 추적한다.
|
||||
>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
- 배경 설명 문서
|
||||
- 의사결정 근거
|
||||
- 최신화 필요
|
||||
- 예: docs/archive/ROADMAP_WBS.md (2026-07-30 archive됨)
|
||||
- 예: docs/ROADMAP_WBS.md
|
||||
|
||||
**Deprecated (신뢰도 0%)**
|
||||
- 폐기된 알고리즘
|
||||
|
||||
@@ -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/ 산출물은 직접 편집하지 않음"
|
||||
- "기존 사용자 변경을 덮어쓰지 않음"
|
||||
@@ -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 개선 로드맵
|
||||
|
||||
**최종 목표**: 신뢰성 높은 자동화된 배포 파이프라인 구축
|
||||
|
||||
@@ -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 현대화 실행 계획
|
||||
**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 현대화 로드맵 (시각화)
|
||||
|
||||
## 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 데이터 기반 고도화 로드맵
|
||||
**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 +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
Reference in New Issue
Block a user