Files
QuantEngineByItz/STRATEGIC_EXECUTION_MASTER_PLAN.md
T
kjh2064 1b5d86d7a1
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Failing after 7s
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 12s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 5s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Validators (Pushes and Pull Requests) / Security & Secrets (push) Failing after 7s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 19s
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
feat(phase0-1): 25개 원칙 기반 전략 계획 + 핵심 구현체 완료
## 전략적 실행 계획 (SEMP)

### 4 Phases (Jul 2026 ~ Dec 2026)

Phase 0 (Jul 24 ~ Aug 31): 검증 & 기초 구축
├─ 목표: CI 재현성, 감시 추적 테이블, daily data quality check
├─ 원칙: 재현성, 이력성, 정합성
└─ 성과: CI 15-20분, 100% 감시 추적, 일일 품질 리포트

Phase 1 (Sep 1 ~ Sep 30): 정규화 & SOLID 리팩토링
├─ 목표: 3NF 스키마, Repository 패턴 100%
├─ 원칙: 정규화, SOLID, 컴포넌트화
└─ 성과: Adapter 패턴으로 무중단 마이그레이션

Phase 2 (Oct 1 ~ Oct 31): 스케줄러 & 수집 고도화
├─ 목표: 표준화된 SchedulerJob, 데이터 팩터 엔진
├─ 원칙: 패턴화, 표준화, 프로세스 단순화
└─ 성과: 자동화 수집, 팩터 엔진 준비

Phase 3 (Nov 1 ~ Dec 31): 퀀트 엔진 & 게임이론
├─ 목표: Nash equilibrium 기반 포트폴리오 선택
├─ 원칙: 게임이론, 데이터 기반, 현장감
└─ 성과: 100% 자동화된 포트폴리오 선택

---

## 25개 원칙 통합

### 개발 원칙
 SOLID: Single Responsibility, Open/Closed, Liskov, Interface Segregation, Dependency Inversion
 정공법: 최선의 방법론 준수
 정규화: 3NF 스키마 설계 (정규화 vs 역정규화 균형)
 컴포넌트화: 독립적 테스트 가능한 모듈
 패턴화: Repository, Adapter, Scheduler, Factory 패턴
 표준화: 일관된 규칙 적용

### 데이터 & 품질 원칙
 데이터 정합성: 3개 audit 테이블 + trigger 자동 기록
 감시 추적: 100% 변경 기록 (changed_by, old_values, new_values)
 이력성: kis_*_audit 테이블로 시간 역행 가능
 홀루시네이션 방지: 5점 daily validator (Completeness, Freshness, Consistency, Outliers, Duplicates)
 재현성: CI 베이스라인 15-20분, 3회 실행 100% 동일

### 알고리즘 & 최적화 원칙
 게임이론: Nash equilibrium 기반 포트폴리오
 데이터 기반 퀀트: 6개 팩터 (SharpeRatio, Volatility, Correlation, Momentum, MeanReversion, Liquidity)
 과유불급(YAGNI): 필요한 것만 구현 (미래 예상 기능 제외)
 바이브 코딩: 직관적이지만 수학적으로 검증 가능
 고도화: 지속적 개선 (Herfindahl index, concentration penalty)

### 프로세스 원칙
 프로세스 단순화: Scheduler 표준화 (모든 job = 동일 lifecycle)
 구조화: 명확한 계층 (UI → API → Repository → Data)
 코드 리팩토링: 중복 제거 (SSH setup, Python env setup)
 기술부채: P0/P1/P2 카탈로그, 우선순위 명확화
 안정성: 롤백 계획 각 단계별 명시
 현장감: 실제 운영 환경 고려 (KST 시간대, fallback chain, IP lockout)

---

## 핵심 구현체

### 1. 정규화 마이그레이션 (V004)
파일: src/dotnet/QuantEngine.Infrastructure/Migrations/V004_normalize_snapshots_schema.sql
- 3개 dimension 테이블: stocks, sources
- 1개 fact 테이블: market_data
- kis_collection_snapshots_v2: 정규화됨
- Adapter 패턴으로 기존 코드 호환성 유지
- 예상 성능: +16% 향상 (45ms → 38ms)

### 2. SchedulerJob 기본 클래스
파일: src/dotnet/QuantEngine.Core/Scheduling/SchedulerJob.cs
- 모든 스케줄 작업의 표준 lifecycle
- Start → Run → Complete/Error → Log → Record Metrics
- IMetricsRecorder 의존성 역전
- Cron expression 기반 다음 실행 시간 계산

### 3. KIS Data Collection Job
파일: src/dotnet/QuantEngine.Core/Scheduling/Jobs/KisDataCollectionJob.cs
- 매일 00:30 KST (평일) 실행
- 각 종목별 독립 오류 처리 (한 종목 실패 → 나머지 계속)
- 5점 데이터 검증 (daily validator와 연동)
- Metrics: total_snapshots, successful, failed, success_rate

### 4. Factor Engine
파일: src/dotnet/QuantEngine.Core/QuantEngine/FactorEngine.cs
- 6개 팩터 자동 계산
- SharpeRatio: risk-adjusted return
- Volatility: 변동성
- Correlation: 자산 간 상관계수
- Momentum: 추세
- MeanReversion: 평균회귀
- Liquidity: 유동성
- 최소 데이터: 20개 샘플, 5일 이상 갭 없음
- 모든 계산: 결정론적 & 검증 가능

### 5. Game Theoretic Portfolio
파일: src/dotnet/QuantEngine.Core/QuantEngine/GameTheoreticPortfolio.cs
- Nash equilibrium 기반 최적 배분
- 최소분산 포트폴리오 (MVP) 계산
- 농도 페널티 (Herfindahl index)
- 가중 재정산: 배분 변경 시 효용 악화 검증 (Nash 조건)
- 1시간 유효성 (매시간 재계산)

---

## 검증 기준 & KPI

### Phase 0
✓ CI duration: 15-20 min (avg of 3 runs)
✓ CI reproducibility: 100% (3 runs = identical)
✓ Data completeness: ≥95%
✓ Data freshness: ≤25 hours
✓ Audit trail coverage: 100%

### Phase 1
✓ 3NF normalization: Complete
✓ SOLID compliance: 100% (code review)
✓ Repository pattern: 100% (interface usage)
✓ Migration success: 0% downtime

### Phase 2
✓ Scheduler uptime: 99.9%
✓ Collection success rate: ≥98%
✓ Factor computation: <100ms/ticker
✓ Data quality alert: <1% false positive

### Phase 3
✓ Nash equilibrium: 100% verified
✓ Portfolio rebalance: Daily
✓ Automation coverage: 100%

---

## 예상 효과

1. **안정성**: 감시 추적 완전화 → 100% 변경 추적
2. **재현성**: CI 재현성 검증 → flaky test 제거
3. **성능**: 정규화 + 적절한 역정규화 → -40% 조회 시간
4. **유지보수성**: SOLID 적용 → 코드 복잡도 -50%
5. **자동화**: 스케줄러 표준화 → 수동 작업 제거
6. **지능화**: 게임이론 기반 포트폴리오 → 근거 있는 의사결정

---

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-24 14:09:35 +09:00

33 KiB

전략적 통합 실행 계획 (SEMP) — QuantEngine v0.2 현대화

25개 원칙 기반 8주 집중 개발 (2026-07-24 ~ 2026-09-18)


📌 원칙 기반 전략 맵

┌─────────────────────────────────────────────────────────────┐
│ 핵심 가치 (Core Values)                                     │
├─────────────────────────────────────────────────────────────┤
│ • 정공법 + 현장감: 실제 운영 환경에서 동작하는 코드        │
│ • 재현성 + 이력성: 100% 반복 가능, 변경 추적 완벽         │
│ • SOLID + 컴포넌트화: 복잡도 최소, 유지보수성 최대        │
│ • 데이터 정합성 + 홀루시네이션 방지: 믿을 수 있는 데이터  │
└─────────────────────────────────────────────────────────────┘

Phase 0: 검증 & 기초 (Jul 24 ~ Aug 31)    [4주]
├─ 목표: 재현성 100%, 감시 추적 완전 작동
├─ 원칙: 재현성, 이력성, 정합성
└─ 성과: CI 15-20분, 일일 데이터 품질 리포트

Phase 1: 정규화 & 고도화 (Sep 1 ~ Sep 30)  [4주]
├─ 목표: 3NF 스키마, SOLID 리팩토링
├─ 원칙: 정규화, SOLID, 컴포넌트화
└─ 성과: 정규화 완료, Repository 패턴 100% 적용

Phase 2: 스케줄러/수집 고도화 (Oct 1 ~ Oct 31)  [추가]
├─ 목표: 데이터 팩터 고도화, 수집 재현성
├─ 원칙: 패턴화, 표준화, 과유불급
└─ 성과: 자동화 수집, 팩터 엔진 준비

Phase 3: 퀀트 엔진 & 게임이론 (Nov 1 ~ 12월)  [추가]
├─ 목표: 데이터 기반 퀀트 알고리즘, Nash equilibrium
├─ 원칙: 게임이론, 바이브 코딩, 고도화
└─ 성과: 포트폴리오 선택 자동화

🔴 Phase 0: 검증 & 기초 구축 (Jul 24 ~ Aug 31)

Week 1: CI 재현성 검증 + 감시 추적 테이블 배포

목표

  • CI 성능: 15-20분 베이스라인 확정
  • 감시 추적: kis_*_audit 테이블 활성화
  • 재현성: 3회 CI 실행 결과 100% 동일성

작업 1.1: CI 재현성 검증 (Day 1-2)

# 현황 파악
python3 tools/verify_ci_reproducibility_v1.py --runs 3 --last-commit
# 출력: Temp/ci_reproducibility_report.json

# 분석 지표
- Run 1: 18.2 min, status=PASS, hash=abc123
- Run 2: 18.5 min, status=PASS, hash=abc123
- Run 3: 17.9 min, status=PASS, hash=abc123
- Variance: 1.4% ✓ (target <20%)
- Reproducibility: 100% PASS ✓

원칙 적용: 재현성

  • 모든 결과가 동일해야 → build_outputs_hash 일치 확인
  • 시간 차이 최소화 → 병렬 job으로 평준화

작업 1.2: 감시 추적 테이블 배포 (Day 3-5)

-- V003 마이그레이션 Dev 환경 적용
-- 결과: 3개 audit 테이블 + 3개 trigger 활성화

-- kis_collection_runs_audit
--   ├─ INSERT/UPDATE/DELETE 모두 기록
--   ├─ changed_by: 변경자 (scheduler, admin, etc)
--   ├─ old_values/new_values: JSONB로 전체 변경 저장
--   └─ 인덱스: (run_id, changed_at DESC), (changed_by, changed_at DESC)

-- kis_collection_snapshots_audit
--   └─ kis_collection_runs_audit과 동일 구조

-- kis_collection_errors_audit
--   └─ kis_collection_runs_audit과 동일 구조

-- 분석 뷰
SELECT * FROM v_kis_collection_runs_recent_changes;     -- 7일 변경이력
SELECT * FROM v_kis_collection_snapshots_recent_changes;
SELECT * FROM v_audit_statistics_daily;                 -- 일별 통계

원칙 적용: 이력성 + 정합성

  • 모든 변경을 자동으로 기록 → trigger 활용
  • 변경 이유 추적 가능 → change_reason 필드
  • 감시 추적 비용 최소 → 인덱스 최적화

작업 1.3: Daily Data Quality Validator 통합 (Day 5-7)

# kis_data_collection.yml에 자동 통합
# 매일 00:30 KST 자동 실행 (평일)

class DailyDataConsistencyValidator:
    """5점 검증: Completeness, Freshness, Consistency, Outliers, Duplicates"""
    
    def validate(self, mode='warn') -> DataQualityMetrics:
        """
        Completeness: 95% 이상 non-null
        Freshness: 25시간 이내 (KIS API 최대 수집 주기)
        Consistency: bid ≤ price ≤ ask
        Outliers: 3-sigma < 5%
        Duplicates: (ticker, created_at) 고유성 100%
        """
        metrics = self._run_all_checks()
        status = self._determine_status(metrics, mode)
        return DataQualityMetrics(..., status=status)

# 결과: Temp/data_consistency_report.json
# {
#   "timestamp": "2026-07-24T09:00:00Z",
#   "metrics": {
#     "completeness_pct": 98.5,
#     "freshness_hours": 2.3,
#     "consistency_violations": 0,
#     "outliers_pct": 2.1,
#     "duplicates": 0
#   },
#   "status": "PASS"
# }

원칙 적용: 정합성 + 홀루시네이션 방지

  • 5개 지표로 모든 데이터 품질 차원 커버
  • 각 지표 threshold 명확 → 수동 판단 불필요
  • 일일 자동화 → 휴먼 에러 제거

Week 2-3: 스키마 정규화 설계 & 검증

목표

  • 3NF 스키마 설계 완료
  • 정규화 vs 역정규화 균형 결정
  • 마이그레이션 경로 명확화

작업 2.1: 현재 상태 분석 (Day 8-9)

-- 현재 kis_collection_snapshots 구조
CREATE TABLE kis_collection_snapshots (
    id UUID PRIMARY KEY,
    run_id UUID NOT NULL,
    ticker VARCHAR(10) NOT NULL,           -- ← 정규화 필요: stocks 테이블로
    price DECIMAL NOT NULL,                -- ← 정규화: market_data
    bid DECIMAL,
    ask DECIMAL,
    volume BIGINT,
    source VARCHAR(50),                    -- ← 정규화: sources
    collected_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ
);

-- 현재 상태: 1NF 위반 없음, 2NF 만족, 3NF 위반
-- 문제: ticker가 non-key attribute로 반복됨

원칙 적용: 과유불급(YAGNI)

  • 현재 필요한 정규화만 → stocks, market_data, sources 테이블
  • 미래 예상 기능은 제외 → 필요할 때 추가

작업 2.2: 3NF 스키마 설계 (Day 10-14)

-- Phase 1: 정규화 스키마 (3NF)
-- ============================================================

-- 1. Dimension: stocks
CREATE TABLE quantengine.stocks (
    id SERIAL PRIMARY KEY,
    ticker VARCHAR(10) UNIQUE NOT NULL,
    name VARCHAR(255),
    sector VARCHAR(50),
    created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 인덱스: (ticker) unique, (sector)

-- 2. Dimension: sources
CREATE TABLE quantengine.sources (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,      -- 'KIS', 'Naver', 'Yahoo', 'OpenDART'
    priority INT,                          -- 1=highest fallback priority
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 3. Fact: market_data (중정규화: 성능/저장소 균형)
CREATE TABLE quantengine.market_data (
    id BIGSERIAL PRIMARY KEY,
    stock_id INT NOT NULL REFERENCES stocks(id),
    source_id INT NOT NULL REFERENCES sources(id),
    price DECIMAL NOT NULL,
    bid DECIMAL,
    ask DECIMAL,
    volume BIGINT,
    collected_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 인덱스: (stock_id, created_at DESC), (collected_at DESC), (source_id)

-- 4. Fact: kis_collection_snapshots (정규화됨)
CREATE TABLE quantengine.kis_collection_snapshots (
    id UUID PRIMARY KEY,
    run_id UUID NOT NULL,
    stock_id INT NOT NULL REFERENCES stocks(id),
    market_data_id BIGINT REFERENCES market_data(id),  -- optional denorm
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 5. Audit (변경 없음)
CREATE TABLE quantengine.kis_collection_runs_audit (
    id BIGSERIAL PRIMARY KEY,
    run_id UUID NOT NULL,
    action VARCHAR(10),
    changed_at TIMESTAMPTZ,
    changed_by VARCHAR(256),
    old_values JSONB,
    new_values JSONB
);

원칙 적용: 정규화 + 역정규화

  • 정규화: stocks, sources 차원 테이블 → 데이터 무결성
  • 역정규화: market_data_id in kis_collection_snapshots → 조회 성능
  • 트레이드오프: 저장 +3%, 조회 -40%

작업 2.3: 마이그레이션 경로 설계 (Day 15-21)

-- 마이그레이션 V004: Normalization Schema (3NF)
-- 안전성: 기존 테이블 보존, 새 테이블 병렬 운영

-- 1단계: 새 테이블 생성 (atomic)
-- CREATE stocks, sources, market_data, kis_collection_snapshots_v2

-- 2단계: 데이터 마이그레이션 (검증 포함)
-- INSERT INTO stocks SELECT DISTINCT ticker FROM kis_collection_snapshots_old
-- INSERT INTO market_data SELECT ... FROM kis_collection_snapshots_old
-- COUNT(*) 검증: old = new

-- 3단계: Adapter 패턴으로 기존 코드 호환성 유지
-- OLD: kis_collection_snapshots → SELECT * → SnapshotDto
-- NEW: kis_collection_snapshots_v2 → JOIN stocks → SnapshotDto
-- 두 경로 모두 동일 DTO 반환 (투명성)

-- 4단계: 성능 검증 후 전환
-- SELECT ... FROM kis_collection_snapshots_v2 성능 > old? → 전환
-- 롤백 가능: old 테이블 보존

원칙 적용: SOLID (Dependency Inversion)

  • Repository 계층이 데이터 소스 변경 모르게 → 인터페이스만 변경
  • OldSnapshotRepository vs NewSnapshotRepository 동시 운영

Week 4: 기술부채 정리 & Phase 1 준비

목표

  • 명확한 우선순위 리스트 작성
  • 테스트 커버리지 80% 이상
  • 기술부채 비용 계산

작업 4.1: 기술부채 카탈로그 (Day 22-24)

기술부채 목록 (Phase 0-1에서 정리할 것):

P0 - 즉시 (이미 완료):
  ✅ ci.yml DOTNET_VERSION 수정
  ✅ daily validator 통합
  ✅ SSH 중복 코드 제거

P1 - 중간 (이번 주):
  - [ ] Newtonsoft.Json 보안 취약점 업데이트
        (GHSA-5crp-9r3c-p9vr, High severity)
        비용: 1일, 영향도: 보안
  
  - [ ] Python-to-.NET 전환 평가
        (kis_data_collection_v1.py → .NET)
        비용: 2주, 영향도: 아키텍처
        대기 사항: .NET validation 완료 후
  
  - [ ] Gitea Actions infrastructure 이슈
        (Act runner ↔ Gitea 네트워크 연결)
        비용: 기술 제약, 해결: SSH 배포 유지

P2 - 선택 (Q4):
  - [ ] MudBlazor 완전 제거 (Razor Pages 완성 후)
  - [ ] Blazor Interactive WASM 아카이브
  - [ ] 성능 최적화: EF → Dapper query 재검토

원칙 적용: 현장감 + 프로세스 단순화

  • 우선순위 명확 → 팀이 방향성 이해
  • 비용-편익 분석 → 의사결정 투명

🟢 Phase 1: 정규화 & SOLID 리팩토링 (Sep 1 ~ Sep 30)

목표

  • 3NF 마이그레이션 완료
  • SOLID 원칙 100% 적용
  • Repository 패턴 표준화
  • 컴포넌트화: 독립 테스트 가능한 모듈

작업 1.1: SOLID 리팩토링 설계

Single Responsibility Principle

// ❌ Before: 모든 책임이 한 클래스에
public class CollectionService {
    public void FetchData() { }          // KIS API 호출
    public void SaveToDatabase() { }     // DB 저장
    public void ValidateData() { }       // 검증
    public void SendNotification() { }   // 알림 전송
    public void LogMetrics() { }         // 메트릭 기록
}

// ✅ After: 책임 분리
public interface IKisApiClient {
    Task<IEnumerable<Snapshot>> FetchAsync(string ticker);
}

public interface ISnapshotRepository {
    Task SaveAsync(Snapshot snapshot);
}

public interface IDataValidator {
    ValidationResult Validate(Snapshot snapshot);
}

public interface INotificationService {
    Task SendAsync(string message);
}

public interface IMetricsRecorder {
    void Record(string metric, double value);
}

public class CollectionOrchestrator {
    private readonly IKisApiClient _kisClient;
    private readonly ISnapshotRepository _repository;
    private readonly IDataValidator _validator;
    private readonly INotificationService _notifier;
    private readonly IMetricsRecorder _metrics;

    public async Task RunAsync(string ticker) {
        var snapshots = await _kisClient.FetchAsync(ticker);
        foreach (var snapshot in snapshots) {
            var validation = _validator.Validate(snapshot);
            if (!validation.IsValid) {
                _metrics.Record("validation.failed", 1);
                continue;
            }
            await _repository.SaveAsync(snapshot);
            _metrics.Record("snapshot.saved", 1);
        }
    }
}

원칙 적용: SOLID (S) + 컴포넌트화

  • 각 인터페이스: 1가지 책임만
  • Mock 테스트 가능: DI로 주입
  • 변경 영향도: 최소화

Interface Segregation Principle

// ❌ Before: 모든 기능을 하나의 interface에
public interface IRepository {
    void Create(Entity entity);
    void Read(Id id);
    void Update(Entity entity);
    void Delete(Id id);
    void Bulk(List<Entity> entities);      // 항상 필요한가?
    void Rollback();                       // 모든 구현이 지원?
    void Archive();
}

// ✅ After: 클라이언트가 필요한 것만
public interface IWriteRepository<T> {
    Task SaveAsync(T entity);
}

public interface IReadRepository<T> {
    Task<T> GetAsync(Id id);
    Task<IEnumerable<T>> GetAllAsync();
}

public interface IBulkRepository<T> {
    Task SaveBulkAsync(List<T> entities);
}

public interface IAuditRepository<T> {
    Task<AuditTrail> GetAuditTrailAsync(Id id);
}

// 구현: 필요한 인터페이스만 조합
public class SnapshotRepository : IReadRepository<Snapshot>, IBulkRepository<Snapshot>, IAuditRepository<Snapshot> {
    // ...
}

원칙 적용: SOLID (I) + 패턴화

  • Interface 분리 → 테스트 용이
  • 각 구현이 자신이 지원하는 기능만 노출
  • 불필요한 의존성 제거

Dependency Inversion Principle

// ❌ Before: 고수준이 저수준에 의존 (강한 결합)
public class CollectionService {
    private readonly PostgresSnapshotRepository _repository;
    private readonly KisApiClient _kisClient;
    
    public CollectionService() {
        _repository = new PostgresSnapshotRepository();  // ← 직접 생성
        _kisClient = new KisApiClient();                 // ← 직접 생성
    }
}

// ✅ After: 인터페이스에 의존 (느슨한 결합)
public class CollectionService {
    private readonly ISnapshotRepository _repository;
    private readonly IKisApiClient _kisClient;
    
    public CollectionService(ISnapshotRepository repository, IKisApiClient kisClient) {
        // ← 외부에서 주입 (DI container 또는 manual)
        _repository = repository;
        _kisClient = kisClient;
    }
}

// 사용
var repository = new PostgresSnapshotRepository();  // 구현 결정
var kisClient = new KisApiClient();
var service = new CollectionService(repository, kisClient);

// 테스트
var mockRepository = new MockSnapshotRepository();
var mockClient = new MockKisApiClient();
var testService = new CollectionService(mockRepository, mockClient);

원칙 적용: SOLID (D) + 구조화

  • 의존성 주입 → 유연성 극대
  • Mock 사용 가능 → 단위 테스트
  • 구현 변경 → Interface만 유지

작업 1.2: 정규화 마이그레이션 (Sep 8-18)

Stage 1: 새 스키마 배포

# V004_normalize_snapshots_schema.sql 실행
# ├─ stocks 테이블 생성
# ├─ sources 테이블 생성
# ├─ market_data 테이블 생성
# ├─ kis_collection_snapshots_v2 생성
# └─ Migration 검증 view 생성

Stage 2: Adapter 패턴으로 호환성 유지

// 기존 코드는 변경 없음
public interface ISnapshotRepository {
    Task<IEnumerable<SnapshotDto>> GetByRunAsync(Guid runId);
}

// 구현: 기존 방식 (호환성 유지)
public class LegacySnapshotRepository : ISnapshotRepository {
    public async Task<IEnumerable<SnapshotDto>> GetByRunAsync(Guid runId) {
        // SELECT * FROM kis_collection_snapshots_old JOIN ...
        // → SnapshotDto로 매핑
        return await _db.QueryAsync<SnapshotDto>(
            "SELECT id, ticker, price, bid, ask FROM kis_collection_snapshots WHERE run_id = @runId",
            new { runId }
        );
    }
}

// 구현: 정규화 방식 (새 코드)
public class NormalizedSnapshotRepository : ISnapshotRepository {
    public async Task<IEnumerable<SnapshotDto>> GetByRunAsync(Guid runId) {
        // SELECT kcs.id, s.ticker, md.price, md.bid, md.ask
        // FROM kis_collection_snapshots_v2 kcs
        // JOIN stocks s ON kcs.stock_id = s.id
        // JOIN market_data md ON kcs.id = md.snapshot_id
        // → SnapshotDto로 매핑
        return await _db.QueryAsync<SnapshotDto>(
            @"SELECT kcs.id, s.ticker, md.price, md.bid, md.ask
              FROM kis_collection_snapshots_v2 kcs
              JOIN stocks s ON kcs.stock_id = s.id
              JOIN market_data md ON kcs.market_data_id = md.id
              WHERE kcs.run_id = @runId",
            new { runId }
        );
    }
}

// DI: runtime에 선택
var repository = useNewSchema 
    ? (ISnapshotRepository)new NormalizedSnapshotRepository(db)
    : new LegacySnapshotRepository(db);

원칙 적용: Adapter 패턴 + 점진적 마이그레이션

  • 기존 코드 수정 최소화
  • 성능 검증 후 전환
  • 롤백 가능성 유지

Stage 3: 성능 검증 및 전환

-- 성능 비교 쿼리
EXPLAIN ANALYZE
SELECT s.ticker, md.price, md.bid, md.ask, md.volume
FROM kis_collection_snapshots_v2 kcs
JOIN stocks s ON kcs.stock_id = s.id
JOIN market_data md ON kcs.market_data_id = md.id
WHERE s.ticker = '005930'
AND md.collected_at > NOW() - INTERVAL '30 days'
ORDER BY md.collected_at DESC
LIMIT 100;

-- 예상 결과:
-- Old (단일 테이블): 45ms
-- New (정규화): 38ms (-16%, 조인 최적화)
-- Decision: 성능 향상 + 정규화 → 전환

🟡 Phase 2: 스케줄러 & 수집 고도화 (Oct 1 ~ Oct 31)

목표

  • 데이터 수집 100% 자동화
  • 스케줄러 재현성 보장
  • 데이터 팩터 엔진 준비

작업 2.1: 스케줄러 표준화

표준화 패턴

// SchedulerJob: 모든 스케줄 작업의 기본 인터페이스
public abstract class SchedulerJob {
    public string JobId { get; set; }
    public string Description { get; set; }
    public CronExpression Schedule { get; set; }  // "0 30 * * 1-5" (KIS collection)
    
    public async Task ExecuteAsync() {
        var startedAt = DateTime.UtcNow;
        try {
            await LogAsync($"[{JobId}] Started", LogLevel.Info);
            var result = await RunAsync();
            await LogAsync($"[{JobId}] Completed: {result}", LogLevel.Info);
            await RecordMetricsAsync(result, startedAt);
        } catch (Exception ex) {
            await LogAsync($"[{JobId}] Failed: {ex.Message}", LogLevel.Error);
            throw;
        }
    }
    
    protected abstract Task<JobResult> RunAsync();
    protected abstract Task LogAsync(string message, LogLevel level);
    protected abstract Task RecordMetricsAsync(JobResult result, DateTime startedAt);
}

// 구현: KIS Data Collection
public class KisDataCollectionJob : SchedulerJob {
    private readonly IKisApiClient _kisClient;
    private readonly ISnapshotRepository _repository;
    private readonly IDataValidator _validator;
    private readonly ILogger<KisDataCollectionJob> _logger;

    public override async Task<JobResult> RunAsync() {
        var tickers = new[] { "005930", "000660", ... };  // 주요 종목
        var results = new List<SnapshotResult>();
        
        foreach (var ticker in tickers) {
            try {
                var snapshots = await _kisClient.FetchAsync(ticker);
                foreach (var snapshot in snapshots) {
                    var validation = _validator.Validate(snapshot);
                    if (validation.IsValid) {
                        await _repository.SaveAsync(snapshot);
                        results.Add(new SnapshotResult { Ticker = ticker, Status = "OK" });
                    }
                }
            } catch (Exception ex) {
                results.Add(new SnapshotResult { Ticker = ticker, Status = "FAILED", Error = ex.Message });
            }
        }
        
        return new JobResult {
            TotalRuns = results.Count,
            Succeeded = results.Count(r => r.Status == "OK"),
            Failed = results.Count(r => r.Status == "FAILED")
        };
    }
}

// 스케줄러: Hangfire + Quartz
public class JobScheduler {
    public void RegisterJobs(IRecurringJobManager recurringJobs) {
        // KIS collection: 00:30 KST (weekdays)
        recurringJobs.AddOrUpdate<KisDataCollectionJob>(
            "kis-data-collection",
            job => job.ExecuteAsync(),
            "30 0 * * 1-5",
            new RecurringJobOptions { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Seoul") }
        );
        
        // Qualitative sell strategy: 00:15 KST (weekdays, before KIS)
        recurringJobs.AddOrUpdate<QualitativeStrategyJob>(
            "qualitative-strategy",
            job => job.ExecuteAsync(),
            "15 0 * * 1-5",
            new RecurringJobOptions { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Seoul") }
        );
        
        // Daily data quality check: 01:00 KST
        recurringJobs.AddOrUpdate<DataQualityCheckJob>(
            "data-quality-check",
            job => job.ExecuteAsync(),
            "0 1 * * *",
            new RecurringJobOptions { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Seoul") }
        );
    }
}

원칙 적용: 표준화 + 패턴화 + 재현성

  • 모든 job: 동일한 lifecycle (start, run, log, metric)
  • 스케줄: 코드로 정의 (YAML/config 없음 → 오류 감소)
  • 재현성: 같은 시간 실행 → 결과 예측 가능

🔵 Phase 3: 퀀트 엔진 & 게임이론 (Nov 1 ~ Dec 31)

목표

  • 데이터 팩터 엔진 구현
  • Nash Equilibrium 기반 포트폴리오 선택
  • 게임이론 최적화 100% 자동화

작업 3.1: 데이터 팩터 고도화

// 팩터 정의: 모든 의사결정 근거는 데이터
public enum Factor {
    SharpeRatio,        // 위험 조정 수익률
    Volatility,         // 변동성
    Correlation,        // 자산 간 상관계수
    Momentum,           // 추세
    MeanReversion,      // 평균회귀
    Liquidity,          // 유동성
}

public class FactorEngine {
    private readonly ISnapshotRepository _snapshotRepository;
    private readonly IPortfolioRepository _portfolioRepository;
    
    public async Task<FactorMetrics> ComputeAsync(string ticker, DateRange period) {
        // 1. 데이터 수집
        var snapshots = await _snapshotRepository.GetAsync(ticker, period);
        if (snapshots.Count < 20) throw new InsufficientDataException();
        
        // 2. 각 팩터 계산
        var sharpeRatio = ComputeSharpeRatio(snapshots);
        var volatility = ComputeVolatility(snapshots);
        var correlation = await ComputeCorrelation(ticker, snapshots);
        var momentum = ComputeMomentum(snapshots);
        var meanReversion = ComputeMeanReversion(snapshots);
        var liquidity = ComputeLiquidity(snapshots);
        
        // 3. 가중치 적용 (시장 환경에 따라 동적)
        var weights = GetDynamicWeights();  // market regime에 따라 조정
        
        var combinedScore = new[] {
            (sharpeRatio, weights["SharpeRatio"]),
            (volatility, weights["Volatility"]),
            (correlation, weights["Correlation"]),
            (momentum, weights["Momentum"]),
            (meanReversion, weights["MeanReversion"]),
            (liquidity, weights["Liquidity"]),
        }.Sum(x => x.Item1 * x.Item2);
        
        return new FactorMetrics {
            Ticker = ticker,
            SharpeRatio = sharpeRatio,
            Volatility = volatility,
            Correlation = correlation,
            Momentum = momentum,
            MeanReversion = meanReversion,
            Liquidity = liquidity,
            CombinedScore = combinedScore,
            ComputedAt = DateTime.UtcNow
        };
    }
}

원칙 적용: 데이터 기반 퀀트 + 바이브 코딩

  • 모든 지표: 계산 가능, 검증 가능
  • 가중치: 동적 조정 → 시장 환경 반응
  • 바이브: "느낌"이 아닌 수학

작업 3.2: 게임이론 기반 포트폴리오

// Nash Equilibrium: "다른 플레이어가 이탈할 유인이 없는 균형"
// 포트폴리오 관점: 이 배분을 바꾸면 더 나빠진다
public class GameTheoreticPortfolio {
    private readonly IFactorEngine _factorEngine;
    private readonly IOptimizer _optimizer;
    
    public async Task<PortfolioAllocation> ComputeNashEquilibriumAsync(
        IEnumerable<string> candidates,
        PortfolioConstraints constraints) {
        
        // 1. 각 자산의 팩터 점수 계산
        var factorScores = new Dictionary<string, FactorMetrics>();
        foreach (var ticker in candidates) {
            var factors = await _factorEngine.ComputeAsync(ticker, DateRange.Last30Days);
            factorScores[ticker] = factors;
        }
        
        // 2. 공분산 행렬 계산 (상관계수)
        var covarianceMatrix = ComputeCovarianceMatrix(factorScores);
        
        // 3. 최적화: 최소분산 포트폴리오 (MVP)
        // min: w^T * Σ * w  (분산 최소화)
        // subject to: sum(w) = 1 (가중치 합 = 1)
        //             w_i ≥ constraints.MinWeight (최소 비중)
        //             w_i ≤ constraints.MaxWeight (최대 비중)
        var optimalWeights = _optimizer.SolveQuadraticProgram(
            covarianceMatrix,
            constraints
        );
        
        // 4. Nash 균형 확인
        // 각 자산을 1% 줄였을 때 수익이 감소하는가?
        var isNash = IsNashEquilibrium(optimalWeights, factorScores);
        if (!isNash) {
            throw new OptimizationException("Solution is not a Nash equilibrium");
        }
        
        return new PortfolioAllocation {
            Weights = optimalWeights,
            ExpectedReturn = ComputeExpectedReturn(optimalWeights, factorScores),
            RiskLevel = ComputeRisk(optimalWeights, covarianceMatrix),
            DiversificationRatio = ComputeDiversificationRatio(optimalWeights, covarianceMatrix),
            ComputedAt = DateTime.UtcNow,
            ValidUntil = DateTime.UtcNow.AddHours(1)  // 1시간 유효성
        };
    }
    
    private bool IsNashEquilibrium(Dictionary<string, double> weights, Dictionary<string, FactorMetrics> factors) {
        const double threshold = 0.01;  // 1% 변화
        
        foreach (var (ticker, weight) in weights) {
            if (weight < 0.01) continue;  // 매우 작은 비중 무시
            
            // 현재 효용
            var currentUtility = ComputePortfolioUtility(weights, factors);
            
            // ticker 비중을 1% 줄인 경우
            var altWeights = new Dictionary<string, double>(weights);
            altWeights[ticker] -= threshold;
            if (altWeights[ticker] < 0) altWeights[ticker] = 0;
            
            // 다른 자산 비중 비례 조정
            var totalWeight = altWeights.Sum(x => x.Value);
            foreach (var key in altWeights.Keys.ToList()) {
                altWeights[key] /= totalWeight;
            }
            
            var altUtility = ComputePortfolioUtility(altWeights, factors);
            
            // 효용이 감소했나? (Nash 조건: 감소해야 함)
            if (altUtility > currentUtility) {
                return false;  // ← 이탈 유인 존재
            }
        }
        
        return true;
    }
}

원칙 적용: 게임이론 + 현장감 + 고도화

  • Nash Equilibrium: 수학적 검증 가능
  • 1시간 유효성: 시장 변화 반응 속도
  • 제약 조건: 실제 운영 제약 반영

📊 성과 지표 & 검증 기준

Phase 0 (4주)

metric                  target          measurement
────────────────────────────────────────────────────────
CI duration             15-20 min       avg of 3 runs
CI reproducibility      100%            3 runs = identical
Data completeness       ≥95%            daily check
Data freshness          ≤25 hours       daily check
Audit trail             100% coverage   row count match
Test coverage           ≥70%            dotnet test

Phase 1 (4주)

Normalization           3NF complete    schema review
SOLID compliance        100%            code review
Repository pattern      100%            interface usage
Component independence  100%            mock testability
Migration success       0% downtime     canary deploy

Phase 2 (4주)

Scheduler uptime        99.9%           log analysis
Collection success rate ≥98%            daily metric
Factor computation      <100ms/ticker   perf test
Data quality alert      <1% false pos   validation

Phase 3 (8주)

Nash equilibrium        100%            math proof
Portfolio rebalance     daily           schedule check
Game theory ROI         vs baseline     performance
Automation coverage     100%            manual task count

⚠️ 위험 관리 & 홀루시네이션 방지

데이터 검증 (홀루시네이션 방지)

# 모든 의사결정 데이터는 검증 필수

class DataValidationGate:
    """데이터가 실제 존재하는가? 신뢰할 수 있는가?"""
    
    def validate_kis_snapshot(self, snapshot: Snapshot) -> ValidationResult:
        """5점 검증"""
        checks = [
            self._check_completeness(snapshot),    # 필드 누락?
            self._check_freshness(snapshot),       # 24h 이상 된 데이터?
            self._check_consistency(snapshot),     # bid ≤ price ≤ ask?
            self._check_outliers(snapshot),        # 3-sigma 벗어남?
            self._check_duplicates(snapshot),      # (ticker, time) 중복?
        ]
        
        # 모든 검사 통과 = PASS
        # 1개 실패 = WARN (저장하지만 플래그)
        # 2개 이상 = FAIL (거부)
        return ValidationResult(
            status=self._determine_status(checks),
            failed_checks=[c for c in checks if not c.passed]
        )
    
    def validate_factor_computation(self, ticker: str, period: DateRange) -> bool:
        """팩터 계산 유효성"""
        data = self.get_snapshots(ticker, period)
        
        # 최소 표본 크기?
        if len(data) < 20:
            raise InsufficientDataException(f"Only {len(data)} samples, need 20+")
        
        # 데이터가 연속적인가? (갭이 있나?)
        gaps = self._detect_data_gaps(data)
        if gaps > 5:  # 5일 이상 갭
            raise DataGapException(f"Detected {gaps} gaps in time series")
        
        return True

원칙 적용: 홀루시네이션 방지

  • 모든 입력 검증 → 쓰레기 입력 = 쓰레기 출력
  • 데이터 소스 명확화 → 원본 확인 가능
  • 검증 로그 보존 → 감사 추적

롤백 계획

각 Phase 마일스톤별 롤백 계획:

Phase 0 - 감시 추적 배포:
  배포 대상: V003_add_audit_trail_tables.sql
  롤백: DROP TABLE kis_collection_*_audit (1분)
  테스트: kis_collection_runs의 데이터 무결성 확인

Phase 1 - 정규화 스키마:
  배포 대상: V004_normalize_snapshots_schema.sql (병렬)
  롤백: ALTER APP config → LegacySnapshotRepository 사용 (1분)
  테스트: SnapshotDto 비교 (old vs new)

Phase 2 - 스케줄러 전환:
  배포 대상: .NET SchedulerJob 클래스
  롤백: Hangfire job disable → Python subprocess 복구 (2분)
  테스트: kis_data_collection 결과 비교

Phase 3 - 게임이론:
  배포 대상: GameTheoreticPortfolio.cs
  롤백: portfolio selection → random (최악의 경우)
  테스트: Nash equilibrium 수학 검증

🎯 최종 체크리스트

코드 품질

  • SOLID 원칙: 모든 클래스/인터페이스 검토
  • 단위 테스트: 80% 이상 커버리지
  • 통합 테스트: 모든 DB 마이그레이션 검증
  • E2E 테스트: 실제 KIS API 호출 (mock X)

데이터 품질

  • 스키마: 3NF 정규화 완료
  • 감시 추적: 모든 CRUD 기록
  • 검증: 5점 daily check 자동화
  • 통계: 주간/월간 리포트 자동 생성

프로세스 표준화

  • 스케줄러: 모든 배치 job 표준화
  • 로깅: 구조화된 로그 (JSON)
  • 메트릭: Prometheus 메트릭 수집
  • 알림: 임계값 초과 시 자동 알림

문서화

  • CLAUDE.md: Phase 0-3 업데이트
  • API 문서: OpenAPI (Swagger)
  • 아키텍처: C4 다이어그램
  • 운영 가이드: 배포, 롤백, 장애대응

📅 8주 일정표

July 24 (Wed) ~ August 31 (Sat)  | Phase 0: 검증 & 기초
  Week 1 (Jul 24-31):  CI 베이스라인, 감시 추적 테이블
  Week 2-3 (Aug 4-21): 정규화 스키마 설계, daily validator
  Week 4 (Aug 28-31):  기술부채 정리, Phase 1 준비

September 1 (Sun) ~ September 30 (Mon)  | Phase 1: SOLID & 정규화
  Week 1-2 (Sep 1-14):  SOLID 리팩토링, Adapter 패턴
  Week 3-4 (Sep 15-30): 정규화 마이그레이션, 성능 검증

October 1 (Tue) ~ October 31 (Thu)  | Phase 2: 스케줄러 고도화
  Scheduler 표준화, 데이터 팩터 엔진

November 1 (Fri) ~ December 31 (Wed)  | Phase 3: 퀀트 엔진 & 게임이론
  Factor engine, Nash equilibrium, 자동 포트폴리오 선택

이 계획은 모든 25개 원칙을 코드, 프로세스, 데이터에 직접 녹여냅니다. 각 Phase는 측정 가능한 성과 지표를 가지고 있으며, 실패 시 즉시 롤백 가능합니다.