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>
This commit is contained in:
@@ -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
|
||||||
@@ -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 파이프라인을 돌려야 한다.
|
||||||
|
|
||||||
@@ -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 개선 로드맵
|
# 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**
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
> **ARCHIVED (2026-07-30)**: OMS·WMS·ERP WBS의 마크다운 버전입니다.
|
||||||
|
> 권위있는 버전은 [`../../spec/60_oms_wms_erp_wbs.yaml`](../../spec/60_oms_wms_erp_wbs.yaml) (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,8 @@
|
|||||||
|
> **ARCHIVED (2026-07-30)**: 본 문서는 QuantEngine 전체 로드맵의 이전 버전(v8.9 기준, 2026-06-13)입니다.
|
||||||
|
> 현재의 마이그레이션 상태는 [`../CLAUDE.md`](../CLAUDE.md)의 Migration Status 섹션과
|
||||||
|
> [`STRATEGIC_EXECUTION_MASTER_PLAN.md`](../STRATEGIC_EXECUTION_MASTER_PLAN.md) (Phase 0-3 상세) 또는
|
||||||
|
> 개별 집중 WBS 문서들(`WBS_10_DOTNET_MIGRATION_HARDENING_2026_06_30.md` 등)을 참고하세요.
|
||||||
|
|
||||||
# 퀀트투자 엔진 — 전체 로드맵 & WBS & 하네스 성공 기준
|
# 퀀트투자 엔진 — 전체 로드맵 & WBS & 하네스 성공 기준
|
||||||
|
|
||||||
> 작성일: 2026-06-13 | 엔진 버전: REBALANCE_ENGINE_V1 기준
|
> 작성일: 2026-06-13 | 엔진 버전: REBALANCE_ENGINE_V1 기준
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user