# 전략적 통합 실행 계획 (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) ```bash # 현황 파악 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) ```sql -- 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) ```python # 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) ```sql -- 현재 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) ```sql -- 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) ```sql -- 마이그레이션 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) ```yaml 기술부채 목록 (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 ```csharp // ❌ 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> 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 ```csharp // ❌ Before: 모든 기능을 하나의 interface에 public interface IRepository { void Create(Entity entity); void Read(Id id); void Update(Entity entity); void Delete(Id id); void Bulk(List entities); // 항상 필요한가? void Rollback(); // 모든 구현이 지원? void Archive(); } // ✅ After: 클라이언트가 필요한 것만 public interface IWriteRepository { Task SaveAsync(T entity); } public interface IReadRepository { Task GetAsync(Id id); Task> GetAllAsync(); } public interface IBulkRepository { Task SaveBulkAsync(List entities); } public interface IAuditRepository { Task GetAuditTrailAsync(Id id); } // 구현: 필요한 인터페이스만 조합 public class SnapshotRepository : IReadRepository, IBulkRepository, IAuditRepository { // ... } ``` **원칙 적용: SOLID (I) + 패턴화** - Interface 분리 → 테스트 용이 - 각 구현이 자신이 지원하는 기능만 노출 - 불필요한 의존성 제거 #### Dependency Inversion Principle ```csharp // ❌ 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: 새 스키마 배포 ```bash # V004_normalize_snapshots_schema.sql 실행 # ├─ stocks 테이블 생성 # ├─ sources 테이블 생성 # ├─ market_data 테이블 생성 # ├─ kis_collection_snapshots_v2 생성 # └─ Migration 검증 view 생성 ``` #### Stage 2: Adapter 패턴으로 호환성 유지 ```csharp // 기존 코드는 변경 없음 public interface ISnapshotRepository { Task> GetByRunAsync(Guid runId); } // 구현: 기존 방식 (호환성 유지) public class LegacySnapshotRepository : ISnapshotRepository { public async Task> GetByRunAsync(Guid runId) { // SELECT * FROM kis_collection_snapshots_old JOIN ... // → SnapshotDto로 매핑 return await _db.QueryAsync( "SELECT id, ticker, price, bid, ask FROM kis_collection_snapshots WHERE run_id = @runId", new { runId } ); } } // 구현: 정규화 방식 (새 코드) public class NormalizedSnapshotRepository : ISnapshotRepository { public async Task> 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( @"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: 성능 검증 및 전환 ```sql -- 성능 비교 쿼리 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: 스케줄러 표준화 #### 표준화 패턴 ```csharp // 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 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 _logger; public override async Task RunAsync() { var tickers = new[] { "005930", "000660", ... }; // 주요 종목 var results = new List(); 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( "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( "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( "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: 데이터 팩터 고도화 ```csharp // 팩터 정의: 모든 의사결정 근거는 데이터 public enum Factor { SharpeRatio, // 위험 조정 수익률 Volatility, // 변동성 Correlation, // 자산 간 상관계수 Momentum, // 추세 MeanReversion, // 평균회귀 Liquidity, // 유동성 } public class FactorEngine { private readonly ISnapshotRepository _snapshotRepository; private readonly IPortfolioRepository _portfolioRepository; public async Task 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: 게임이론 기반 포트폴리오 ```csharp // Nash Equilibrium: "다른 플레이어가 이탈할 유인이 없는 균형" // 포트폴리오 관점: 이 배분을 바꾸면 더 나빠진다 public class GameTheoreticPortfolio { private readonly IFactorEngine _factorEngine; private readonly IOptimizer _optimizer; public async Task ComputeNashEquilibriumAsync( IEnumerable candidates, PortfolioConstraints constraints) { // 1. 각 자산의 팩터 점수 계산 var factorScores = new Dictionary(); 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 weights, Dictionary 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(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 ``` --- ## ⚠️ 위험 관리 & 홀루시네이션 방지 ### 데이터 검증 (홀루시네이션 방지) ```python # 모든 의사결정 데이터는 검증 필수 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 ``` **원칙 적용: 홀루시네이션 방지** - 모든 입력 검증 → 쓰레기 입력 = 쓰레기 출력 - 데이터 소스 명확화 → 원본 확인 가능 - 검증 로그 보존 → 감사 추적 ### 롤백 계획 ```yaml 각 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는 측정 가능한 성과 지표를 가지고 있으며, 실패 시 즉시 롤백 가능합니다.**