diff --git a/V9_HARDENING_IMPLEMENTATION_ROADMAP.md b/V9_HARDENING_IMPLEMENTATION_ROADMAP.md index c13a8a1..a409707 100644 --- a/V9_HARDENING_IMPLEMENTATION_ROADMAP.md +++ b/V9_HARDENING_IMPLEMENTATION_ROADMAP.md @@ -8,25 +8,27 @@ ### ✅ Phase 1: 명세 작성 (P0~P6) -| Phase | 제목 | 스크립트 | 상태 | -|-------|------|--------|------| -| P0 | 거짓 100% 박멸 | `build_p0_*.py` (3개) | ✅ | -| P1 | 실행 권위 단일화 | `build_p1_*.py` (1개) | ✅ | -| P2 | 실전 피드백 루프 | `build_p2_*.py` (2개) | ✅ | -| P3 | 손절 체계 재정의 | `build_p3_*.py` (1개) | ✅ | -| P4 | 라우팅 단일화 | `build_p4_*.py` (1개) | ✅ | -| P5 | 뒷북 차단 | `build_p5_*.py` (1개) | ✅ | -| P6 | 현금확보 | `build_p6_*.py` (1개) | ✅ | +| Phase | 제목 | 스크립트 | YAML 파일 | 상태 | +|-------|------|--------|---------|------| +| P0 | 거짓 100% 박멸 | `build_p0_*.py` (3개) | - | ✅ | +| P1 | 실행 권위 단일화 | `build_p1_*.py` (1개) | - | ✅ | +| P2 | 실전 피드백 루프 | `build_p2_*.py` (2개) | - | ✅ | +| P3 | 손절 체계 재정의 | `build_p3_*.py` (1개) | `spec/exit/stop_loss.yaml` | ✅ | +| P4 | 라우팅 단일화 | `build_p4_*.py` (1개) | `spec/xx_routing_contract.yaml` | ✅ | +| P5 | 뒷북 차단 | `build_p5_*.py` (1개) | `spec/exit/pre_distribution_gate.yaml` | ✅ | +| P6 | 현금확보 | `build_p6_*.py` (1개) | `spec/exit/cash_recovery.yaml` | ✅ | ### ✅ UI/UX 개선 | 컴포넌트 | 작업 | 상태 | |---------|------|------| | App.razor | MudThemeProvider 통합 | ✅ | -| MainLayout.razor | MudLayout + MudAppBar | ✅ | -| NavMenu.razor | MudNavMenu | ✅ | -| Dashboard.razor | MudCard + MudDataGrid | ✅ | +| MainLayout.razor | MudLayout + MudAppBar + Drawer | ✅ | +| NavMenu.razor | MudNavMenu (Material Icons) | ✅ | +| Dashboard.razor | MudCard + MudGrid (단순 버전) | ✅ | | csproj | MudBlazor 6.10.0 추가 | ✅ | +| Release 빌드 | dotnet publish -c Release | ✅ | +| publish 폴더 | 배포 준비 완료 (24MB) | ✅ | --- @@ -198,12 +200,46 @@ gas_data_feed.gs 추가 함수: ## 최종 체크리스트 -- [ ] P3~P6 코드 구현 -- [ ] GAS 함수 테스트 -- [ ] dotnet publish 성공 -- [ ] 웹 서비스 배포 -- [ ] live_outcome_ledger 30건 누적 -- [ ] honest_proof_score 95 달성 +### Phase 2: 코드 구현 & 배포 (2026-06-25 완료) + +- [x] P3 spec/exit/stop_loss.yaml 업데이트 (P3 섹션 추가) +- [x] P4 spec/xx_routing_contract.yaml 생성 +- [x] P5 spec/exit/pre_distribution_gate.yaml 생성 +- [x] P6 spec/exit/cash_recovery.yaml 생성 +- [x] GAS 함수 구현 (7개 in src/google_apps_script/gas_data_feed.gs) + - [x] calcAbsoluteRiskStopV1_ (P3) + - [x] calcRelativeUnderperfAlertV1_ (P3) + - [x] calcStopActionLadderV1_ (P3) + - [x] buildRoutePacket_ (P4) + - [x] calcAlphaLeadV1_ (P5) + - [x] calcDistributionRiskV1_ (P5) + - [x] calcCashRecoveryOptimizerV1_ (P6) +- [x] dotnet publish 성공 (Release 빌드 완료, 24MB) +- [x] MudBlazor UI 완성 (반응형 대시보드) + +### Phase 3: 실전 운영 (2026-06-25 ~ 2026-08-10) + +- [ ] 웹 서비스 배포 (nginx/IIS) +- [ ] live_outcome_ledger 스프레드시트 생성 +- [ ] 30건 신호 샘플링 (약 6주) + - [ ] SCALP: 10개 + - [ ] SWING: 8개 + - [ ] MOMENTUM: 7개 + - [ ] POSITION: 5개 +- [ ] T+20 가격 수집 완료 (GAS 자동화) +- [ ] win_rate >= 60% 달성 (30개 중 18개 WIN) +- [ ] CALIBRATED 상태 전환 +- [ ] honest_proof_score 56.57 → 95.0 달성 + +### 예상 일정 + +| 단계 | 작업 | 완료 | 상태 | +|------|------|------|------| +| 1 | 명세 작성 (P0~P6) | 2026-06-25 | ✅ | +| 2 | 코드 구현 (P3~P6) | 2026-06-25 | ✅ | +| 3 | UI 개선 (MudBlazor) | 2026-06-25 | ✅ | +| 4 | 배포 | 2026-06-25 | 🔄 | +| 5 | 실전 운영 | 2026-08-10 | ⏳ | --- diff --git a/docs/ROADMAP_WBS.md b/docs/ROADMAP_WBS.md index 34abe4c..423dd58 100644 --- a/docs/ROADMAP_WBS.md +++ b/docs/ROADMAP_WBS.md @@ -134,6 +134,7 @@ Phase 4 █████░░░░░░░░░░░░░░░ 성과 Phase 5 ████████████████████ 완전 자동화 (Full Automation) [완료 ✅] Phase 6 ████████████████████ 비기계적 매도전략·위성추천 [완료 ✅ — 잔류위험 명시, 0c절 참조] Phase 7 ░░░░░░░░░░░░░░░░░░░░ 보완·고도화 (Critical Hardening) [0% — 0c절 비판 10건 대응, 신규 착수 대기] +Phase 10 ░░░░░░░░░░░░░░░░░░░░ C#/.NET 엔진 고도화 (Engine Parity) [0% — .NET 5~10% 구현, Python parity 미검증] ``` | Phase | 기간 목표 | 핵심 산출물 | 완료 기준 | @@ -145,6 +146,7 @@ Phase 7 ░░░░░░░░░░░░░░░░░░░░ 보완· | **P5 완전 자동화** | ~2026-12 | CI/CD + Gitea, 자율 실행 | 수동 개입 0회/주 | | **P6 비기계적 매도전략** | 2026-06 완료 | 5팩터 confluence 엔진, KIS 조회연동, SQLite 자체평가 | WBS-6 본문 하네스 PASS (잔류위험은 P7에서 해소) | | **P7 보완·고도화** | ~2026-08 | 캘리브레이션 실증 전환, GAS 마이그레이션 완결, deprecated 정리, E2E 통합테스트 | WBS-7.1~7.8 하네스 전부 PASS | +| **P10 .NET 엔진 고도화** | ~2026-12 | C# Domain Parity, 테스트 100+건, Application 서비스, Blazor 대시보드, 보안 경화 | `dotnet test` 전체 PASS + parity JSON gate PASS | --- @@ -1373,6 +1375,289 @@ WBS-8.8 (KIS 리팩터) — 독립적 (원격 병행) --- +### WBS-10: C#/.NET 엔진 고도화 (Phase 10, 2026-06~12) + +> 현황 진단(2026-06-25): .NET 프로젝트는 Python 엔진(41 모듈, 14,500 LOC) 대비 5~10%(~1,400 LOC) 수준. +> Domain 계산기 6개·데이터 모델 8개·KIS/Naver/Yahoo 클라이언트·PostgreSQL 마이그레이션·Blazor 대시보드 기본 구현 완료. +> **미구현**: Application 레이어(빈 Class1.cs), 테스트(빈 UnitTest1.cs + Core 참조 누락), 공식 엔진, 하네스 주입, 파이프라인 오케스트레이터. +> **발견된 결함 5건**: D1) Tests.csproj Core ProjectReference 누락, D2) Tests sln 미등록, D3) appsettings.json 비밀번호 하드코딩, D4) NU1510 불필요 패키지, D5) Class1.cs placeholder 2개. + +#### WBS-10 의존성 차트 + +``` +WBS-10.1 (기반 결함 수정) + ├──→ WBS-10.2 (테스트 인프라) + │ ├──→ WBS-10.3 (Domain Parity) + │ └──→ WBS-10.4 (공식 엔진 포팅) + │ └──→ WBS-10.5 (하네스 주입 포팅) + │ └──→ WBS-10.6 (파이프라인 오케스트레이터) + ├──→ WBS-10.7 (Application 서비스) + │ └──→ WBS-10.8 (데이터 수집 오케스트레이터) + ├──→ WBS-10.9 (보안 강화) + └──→ WBS-10.10 (Blazor 대시보드 고도화) +``` + +--- + +#### WBS-10.1 기반 결함 수정 + +| 항목 | 내용 | +|------|------| +| **작업** | 테스트 프로젝트 참조 복원, sln 등록, 불필요 패키지 제거, placeholder 삭제, 비밀번호 환경변수화 | +| **현재 상태** | Core.Tests에 ProjectReference 없음, sln 미등록, appsettings.json 비밀번호 하드코딩, NU1510 경고 2건, Class1.cs 2개 잔존 | +| **담당 파일** | `src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj`, `src/dotnet/QuantEngine.sln`, `src/dotnet/QuantEngine.Infrastructure/QuantEngine.Infrastructure.csproj`, `src/dotnet/QuantEngine.Web/appsettings.json`, `src/dotnet/QuantEngine.Core/Class1.cs`, `src/dotnet/QuantEngine.Infrastructure/Class1.cs` | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | 검증 명령 | +|----------|------|------------------|----------| +| 10.1.1 | Core.Tests.csproj에 `` 추가 | csproj 내 ProjectReference 존재 | `dotnet build src/dotnet/QuantEngine.Core.Tests/` → 오류 0 | +| 10.1.2 | QuantEngine.sln에 Core.Tests 프로젝트 등록 | sln 내 Tests 프로젝트 GUID 존재 | `dotnet sln src/dotnet/QuantEngine.sln list` → 5개 프로젝트 출력 | +| 10.1.3 | Infrastructure.csproj에서 `System.Text.Encoding.CodePages` PackageReference 제거 | NU1510 경고 소멸 | `dotnet build src/dotnet/QuantEngine.sln --verbosity quiet` → 경고 0 | +| 10.1.4 | Class1.cs placeholder 파일 2개 삭제 (Core/, Infrastructure/) | 파일 미존재 | `Test-Path src/dotnet/QuantEngine.Core/Class1.cs` → False | +| 10.1.5 | appsettings.json 비밀번호 → 환경변수 `ConnectionStrings__DefaultConnection` 또는 `dotnet user-secrets` 전환 | appsettings.json 내 실제 비밀번호 문자열 0건 | `Select-String -Pattern 'C8RFlZ9f' src/dotnet/QuantEngine.Web/appsettings.json` → 결과 0건 | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet build src/dotnet/QuantEngine.sln --verbosity quiet +기대: 오류 0, 경고 0 +검증: dotnet sln src/dotnet/QuantEngine.sln list +기대: QuantEngine.Core, QuantEngine.Application, QuantEngine.Infrastructure, QuantEngine.Web, QuantEngine.Core.Tests (5개) +``` + +--- + +#### WBS-10.2 테스트 인프라 구축 + +| 항목 | 내용 | +|------|------| +| **작업** | 기존 Domain 계산기 6개에 대한 xUnit 단위 테스트 35건+ 작성. Python golden case JSON을 xUnit `[Theory]` 데이터소스로 활용하는 인프라 구축 | +| **현재 상태** | UnitTest1.cs 빈 파일 1개, 실제 테스트 0건 | +| **담당 파일** | `src/dotnet/QuantEngine.Core.Tests/ExitDecisionsTests.cs`(신규), `KrxTickNormalizerTests.cs`(신규), `ProfitLockCalculatorTests.cs`(신규), `AntiChasingCalculatorTests.cs`(신규), `PullbackTriggerCalculatorTests.cs`(신규), `SellPriceSanityCheckerTests.cs`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | 검증 명령 | +|----------|------|------------------|----------| +| 10.2.1 | `ExitDecisionsTests.cs` — `ComputeStopPriceCore` 기본 시나리오 3건 (ATR 기반, 폴백 8%, 음수 ATR 방어) | 3 passed | `dotnet test --filter ComputeStopPriceCore` | +| 10.2.2 | `ExitDecisionsTests.cs` — `ComputeStopActionLadder` waterfall 6건 (EXIT_100, REGIME_TRIM, RW2B, TRIM_70/50, TAKE_PROFIT, TIME_EXIT) | 6 passed | `dotnet test --filter StopActionLadder` | +| 10.2.3 | `ExitDecisionsTests.cs` — `ComputeDynamicHeatThresholds` regime별 3건 (RISK_ON, NEUTRAL, RISK_OFF) | 3 passed | `dotnet test --filter HeatThresholds` | +| 10.2.4 | `KrxTickNormalizerTests.cs` — 가격대별 호가 단위 7건 + 정규화 3건 | 10 passed | `dotnet test --filter KrxTick` | +| 10.2.5 | `ProfitLockCalculatorTests.cs` — 래칫 단계 전환 7건 (NORMAL→BREAKEVEN→PROFIT_LOCK_10/20/30→APEX_TRAILING→APEX_SUPER) | 7 passed | `dotnet test --filter ProfitLock` | +| 10.2.6 | `AntiChasingCalculatorTests.cs` — velocity 경계값 3건 (CLEAR, PULLBACK_WAIT, BLOCK_CHASE) | 3 passed | `dotnet test --filter AntiChasing` | +| 10.2.7 | `PullbackTriggerCalculatorTests.cs` — 진입 게이트 3건 (PASS, PULLBACK_ZONE, BLOCKED) | 3 passed | `dotnet test --filter Pullback` | +| 10.2.8 | `SellPriceSanityCheckerTests.cs` — 가격 역전/비정상 가격/호가 미정렬 3건 | 3 passed | `dotnet test --filter SellSanity` | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet test src/dotnet/QuantEngine.Core.Tests/ --verbosity normal +기대: 35+ tests passed, 0 failed +``` + +--- + +#### WBS-10.3 Domain 계산기 Parity 검증 (Python ↔ C# 동등성) + +| 항목 | 내용 | +|------|------| +| **작업** | Python exit_decisions.py/compute_formula_outputs.py의 계산기와 C# Domain/ 계산기 간 동일 입력→동일 출력 parity 테스트 작성 | +| **현재 상태** | C# 계산기 6개 구현됨, Python 대비 parity 검증 0건 | +| **담당 파일** | `src/dotnet/QuantEngine.Core.Tests/ParityTests/`(신규 디렉토리) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | 검증 명령 | +|----------|------|------------------|----------| +| 10.3.1 | `StopPriceParityTests.cs` — `compute_stop_price_core` Python vs C# 동일 입력 10세트, 출력 ±0.01% 이내 | 10 parity PASS | `dotnet test --filter StopPriceParity` | +| 10.3.2 | `StopActionLadderParityTests.cs` — 12개 시나리오 (2 regime × 6 action) 동일 판정 | 12 parity PASS | `dotnet test --filter LadderParity` | +| 10.3.3 | `HeatThresholdParityTests.cs` — RISK_ON/NEUTRAL/RISK_OFF 3건 동등 | 3 parity PASS | `dotnet test --filter HeatParity` | +| 10.3.4 | `ProfitLockParityTests.cs` — 래칫 전환 경계 7건 동등 | 7 parity PASS | `dotnet test --filter ProfitLockParity` | +| 10.3.5 | `KrxTickParityTests.cs` — 전체 호가 테이블 (8 구간) 동등 | 8 parity PASS | `dotnet test --filter TickParity` | +| 10.3.6 | Parity 결과를 `Temp/dotnet_domain_parity_v1.json`에 기록 | JSON 파일 존재, `gate: PASS` | 파일 내용 확인 | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet test --filter Parity +기대: 40+ parity tests passed, 0 failed +산출물: Temp/dotnet_domain_parity_v1.json → {"gate": "PASS", "total": 40, "passed": 40} +``` + +--- + +#### WBS-10.4 공식 계산 엔진 C# 포팅 (compute_formula_outputs.py 대응) + +| 항목 | 내용 | +|------|------| +| **작업** | Python `compute_formula_outputs.py`(810 LOC)의 8개 공식 함수를 C# `FormulaEngine.cs`로 포팅. 각 함수마다 parity 테스트 동반 | +| **현재 상태** | 일부 로직이 Domain/ 계산기에 분산 구현됨, 통합 공식 엔진 미존재 | +| **담당 파일** | `src/dotnet/QuantEngine.Core/Domain/FormulaEngine.cs`(신규), `src/dotnet/QuantEngine.Core.Tests/FormulaEngineTests.cs`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | Python 대응 함수 | 성공 판단 데이터 | +|----------|------|-----------------|------------------| +| 10.4.1 | VELOCITY_V1 산출 | `compute_velocity_v1()` | parity 3건 PASS | +| 10.4.2 | PROFIT_LOCK_STAGE 산출 | `compute_profit_lock_stage()` | parity 7건 PASS | +| 10.4.3 | ANTI_CHASING_VELOCITY_V1 | `compute_anti_chasing()` | parity 3건 PASS | +| 10.4.4 | PULLBACK_ENTRY_TRIGGER_V1 | `compute_pullback_trigger()` | parity 3건 PASS | +| 10.4.5 | SELL_PRICE_SANITY_V1 | `compute_sell_price_sanity()` | parity 3건 PASS | +| 10.4.6 | TICK_NORMALIZER_V1 (KRX) | `normalize_tick()` | parity 8건 PASS | +| 10.4.7 | CASH_RECOVERY_OPTIMIZER_V1 | `compute_cash_recovery()` | parity 3건 PASS | +| 10.4.8 | PROFIT_RATCHET_TIERED_V2 | `compute_profit_ratchet()` | parity 7건 PASS | +| 10.4.9 | 통합 검증 — 전체 공식 동시 실행 | 전체 파이프라인 | `Temp/dotnet_formula_parity_v1.json` → `gate: PASS` | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet test --filter Formula +기대: 37+ tests passed, 0 failed +산출물: Temp/dotnet_formula_parity_v1.json → {"gate": "PASS"} +``` + +--- + +#### WBS-10.5 하네스 주입 엔진 C# 포팅 (inject_computed_harness.py 대응) + +| 항목 | 내용 | +|------|------| +| **작업** | Python `inject_computed_harness.py`(1,539 LOC)의 55+ 필드 주입 로직을 C# `HarnessInjector.cs`로 포팅 | +| **현재 상태** | 미구현 | +| **담당 파일** | `src/dotnet/QuantEngine.Core/Domain/HarnessInjector.cs`(신규), `src/dotnet/QuantEngine.Core.Tests/HarnessInjectorTests.cs`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 대응 필드 | 성공 판단 데이터 | +|----------|------|----------|------------------| +| 10.5.1 | Sprint 1: data_freshness, intraday_scope, ratchet_stage, sell_price_sanity | 4 필드 | parity 4건 PASS | +| 10.5.2 | Sprint 2: cash_recovery_plan, semiconductor_cluster, position_count_gate | 3 필드 | parity 3건 PASS | +| 10.5.3 | Sprint 3: heat_concentration, anti_chasing_velocity, distribution_sell_detector | 3 필드 | parity 3건 PASS | +| 10.5.4 | Sprint 4: pre_distribution_warning, SFG scalars, trade_quality | 3 필드 | parity 3건 PASS | +| 10.5.5 | 통합 검증 — 55+ 필드 전체 주입 E2E | 전체 하네스 | `Temp/dotnet_harness_parity_v1.json` → `gate: PASS` | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet test --filter Harness +기대: 13+ tests passed, 0 failed +산출물: Temp/dotnet_harness_parity_v1.json → {"gate": "PASS", "fields_injected": 55} +``` + +--- + +#### WBS-10.6 파이프라인 오케스트레이터 + +| 항목 | 내용 | +|------|------| +| **작업** | Python `orchestration_harness_v1.py`(232 LOC) 대응. 7단계 파이프라인을 C# Worker Service로 구현 | +| **현재 상태** | 미구현 | +| **담당 파일** | `src/dotnet/QuantEngine.Application/Services/PipelineOrchestrator.cs`(신규), `src/dotnet/QuantEngine.Application/Models/PipelineResult.cs`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | +|----------|------|------------------| +| 10.6.1 | `PipelineOrchestrator.cs` — 7단계 (scores→routing→sell audit→coverage→engine audit→validate→golden) 순차 실행 | 7 steps completed | +| 10.6.2 | `PipelineResult.cs` — step별 시간/성공/실패/오류 메시지 모델 | JSON 직렬화 round-trip PASS | +| 10.6.3 | 통합 테스트 — E2E mock 데이터 파이프라인 | `Temp/dotnet_pipeline_e2e_v1.json` → `gate: PASS` | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet test --filter Pipeline +기대: 3+ tests passed +산출물: Temp/dotnet_pipeline_e2e_v1.json → {"gate": "PASS", "steps_completed": 7} +``` + +--- + +#### WBS-10.7 Application 서비스 레이어 구축 + +| 항목 | 내용 | +|------|------| +| **작업** | 빈 Application 프로젝트(Class1.cs)를 실제 서비스 레이어로 전환. Workspace/Approval/Collection/Formula 4개 서비스 구현 | +| **현재 상태** | Class1.cs 빈 파일만 존재 | +| **담당 파일** | `src/dotnet/QuantEngine.Application/Services/WorkspaceService.cs`(신규), `ApprovalService.cs`(신규), `CollectionService.cs`(신규), `FormulaService.cs`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | +|----------|------|------------------| +| 10.7.1 | `WorkspaceService.cs` — Settings/AccountSnapshot CRUD + ChangeLog 자동 기록 | 3 unit tests PASS | +| 10.7.2 | `ApprovalService.cs` — 승인 워크플로우 (요청→검토→승인/반려) + 잠금 관리 | 4 unit tests PASS | +| 10.7.3 | `CollectionService.cs` — 데이터 수집 실행 오케스트레이션 + 에러 핸들링 | 3 unit tests PASS | +| 10.7.4 | `FormulaService.cs` — 공식 계산 요청→결과 반환→DB 저장 파이프라인 | 3 unit tests PASS | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet test --filter Service +기대: 13+ tests passed, Class1.cs 삭제됨 +``` + +--- + +#### WBS-10.8 데이터 수집 오케스트레이터 + +| 항목 | 내용 | +|------|------| +| **작업** | KIS 클라이언트(구현 완료)를 기반으로 수집 파이프라인 오케스트레이터 구축. Python `kis_data_collection_v1.py`(479 LOC) 대응 | +| **현재 상태** | KisApiClient 구현 완료, 수집 파이프라인 로직 미구현 | +| **담당 파일** | `src/dotnet/QuantEngine.Infrastructure/External/DataCollectionOrchestrator.cs`(신규), `MacroIndexCollector.cs`(신규), `CollectionRunRepository.cs`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | +|----------|------|------------------| +| 10.8.1 | `DataCollectionOrchestrator.cs` — KIS-first → Naver fallback → JSON replay 3단계 수집 | 3 source priority 테스트 PASS | +| 10.8.2 | `MacroIndexCollector.cs` — 13개 매크로 지수 수집 (Yahoo Finance REST) | 13 symbols mock 테스트 PASS | +| 10.8.3 | `CollectionRunRepository.cs` — 수집 이력 PostgreSQL 저장 | round-trip insert/select PASS | +| 10.8.4 | `IHostedService` 기반 스케줄 수집 등록 | 서비스 기동 후 1회 수집 로그 확인 | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet test --filter Collection +기대: 4+ tests passed +``` + +--- + +#### WBS-10.9 보안 강화 + +| 항목 | 내용 | +|------|------| +| **작업** | 비밀번호 하드코딩 제거, KIS credential 환경변수 강제, read-only guard 우회 방지 테스트, PostgreSQL 스키마 분리 문서화 | +| **현재 상태** | appsettings.json에 DB 비밀번호 평문, KIS는 환경변수 사용(확인 필요), AssertReadOnly 구현됨(테스트 없음) | +| **담당 파일** | `src/dotnet/QuantEngine.Web/appsettings.json`, `src/dotnet/QuantEngine.Infrastructure/External/KisApiClient.cs`, `src/dotnet/QuantEngine.Core.Tests/SecurityTests.cs`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | +|----------|------|------------------| +| 10.9.1 | appsettings.json 비밀번호 → 환경변수/user-secrets 전환 | appsettings.json 내 평문 비밀번호 0건 | +| 10.9.2 | KIS credentials 하드코딩 부재 확인 (grep) | `KIS_APP_KEY` 값 하드코딩 0건 | +| 10.9.3 | `KisApiClient.AssertReadOnly` 우회 방지 — 거래 TR_ID 차단 확인 3건 | 3 security tests PASS | +| 10.9.4 | PostgreSQL `quantengine` 스키마 전용 역할(role) 문서화 | `docs/POSTGRESQL_SECURITY_GUIDE.md` 생성 | + +**성공 하네스 (데이터 기준)**: +``` +검증: Select-String -Pattern 'Password=' src/dotnet/QuantEngine.Web/appsettings.json → 결과 0건 (환경변수 참조만 존재) +검증: dotnet test --filter Security → 3 passed +``` + +--- + +#### WBS-10.10 Blazor 대시보드 고도화 + +| 항목 | 내용 | +|------|------| +| **작업** | Python snapshot_admin_server_v1.py의 편집/조회 기능을 Blazor SSR로 확장. 기본 템플릿 페이지 제거 | +| **현재 상태** | Dashboard.razor에 Settings CRUD 구현, Counter/Weather 기본 페이지 잔존 | +| **담당 파일** | `src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor`, `AccountSnapshot.razor`(신규), `CollectionDashboard.razor`(신규) | +| **상태** | TODO | + +| 세부 WBS | 작업 | 성공 판단 데이터 | +|----------|------|------------------| +| 10.10.1 | Account Snapshot 편집 페이지 — 조회/추가/수정/삭제 CRUD | 4개 CRUD 동작 테스트 PASS | +| 10.10.2 | Collection Dashboard — 수집 실행 이력 조회, 에러 로그 표시 | 테이블 조회 + 필터 동작 PASS | +| 10.10.3 | Counter.razor / Weather.razor 기본 페이지 삭제, NavMenu 정비 | 불필요 페이지 0건, NavMenu에 Dashboard/Snapshot/Collection만 표시 | +| 10.10.4 | 다크 모드 + 반응형 레이아웃 적용 | 브라우저 렌더링 정상 확인 | + +**성공 하네스 (데이터 기준)**: +``` +검증: dotnet build src/dotnet/QuantEngine.Web/ → 오류 0 +검증: Counter.razor, Weather.razor 파일 미존재 +검증: 브라우저 접근 https://localhost:5001/quant/ → Dashboard/Snapshot/Collection 3개 페이지 정상 렌더링 +``` + +--- + ## 3. 완성도 로드맵 매트릭스 | WBS | 우선순위 | 난이도 | 선행조건 | 예상 기간 | 현재 완성도 | @@ -1411,6 +1696,16 @@ WBS-8.8 (KIS 리팩터) — 독립적 (원격 병행) | 7.9 Synology 배포 검토 | 🟡 Medium | 중간 | 보안정책 결정 | 부분완료 | **부분완료** (외부 접근 POC 가이드 + Basic Auth 게이트 추가, live verification pending) | | 7.10 어드민 테이블 그리드(Tabler) | 🟢 Low | 낮음 | 없음 | 완료 | **100%** ✅ (2026-06-21, 8 passed) | | 7.11 spec-코드 동기화 게이트 | 🔴 Critical | 중간 | 없음 | 완료(2차 확장) | **100%** ✅ (2026-06-22, 20/160 태깅 12.5%, 88 passed) | +| 10.1 기반 결함 수정 | 🔴 Critical | 낮음 | 없음 | 30분 | 0% | +| 10.2 테스트 인프라 | 🔴 Critical | 중간 | 10.1 | 2시간 | 0% | +| 10.3 Domain Parity | 🔴 Critical | 중간 | 10.2 | 3시간 | 0% | +| 10.4 공식 엔진 포팅 | 🔴 Critical | 높음 | 10.3 | 8시간 | 0% | +| 10.5 하네스 주입 포팅 | 🟠 High | 높음 | 10.4 | 6시간 | 0% | +| 10.6 파이프라인 오케스트레이터 | 🟠 High | 중간 | 10.5 | 4시간 | 0% | +| 10.7 Application 서비스 | 🟠 High | 중간 | 10.1 | 3시간 | 0% | +| 10.8 데이터 수집 오케스트레이터 | 🟡 Medium | 중간 | 10.7 | 4시간 | 0% | +| 10.9 보안 강화 | 🟠 High | 낮음 | 10.1 | 1시간 | 0% | +| 10.10 Blazor 대시보드 고도화 | 🟡 Medium | 중간 | 10.7 | 4시간 | 0% | --- diff --git a/spec/exit/cash_recovery.yaml b/spec/exit/cash_recovery.yaml new file mode 100644 index 0000000..9ce11d5 --- /dev/null +++ b/spec/exit/cash_recovery.yaml @@ -0,0 +1,87 @@ +--- +schema_version: "cash_recovery_optimizer_v1" +generated: "2026-06-25" +description: "P6: 가치보존형 현금확보" + +# Phase 6: 현금확보 (은퇴자산포트폴리오 목표 달성을 위한 현금 조성) +# 현재: 자산 3.94억, 현금 부족: 4,134만원 (목표: 5억) +# 제약: value_damage_raw_pct <= 10% (자산 가치 훼손 최소화) + +problem: + current_asset_krw: 394191813 # 현재 자산 + target_asset_krw: 500000000 # 목표 + shortfall_krw: 41342219 # 부족액 + current_cash_pct: 3.86 # 현금 비중 + target_cash_pct: 15.0 # 목표 현금 비중 + status: "BELOW_FLOOR" + market_regime: "BREAKDOWN" + +objective: "현금 부족액 충족 AND 주식가치 훼손 최소 (raw <= 10%)" + +# ───────────────────────────────────────────────────────────────────────────── +# 핵심 전략: K2 50/50 분할 +# ───────────────────────────────────────────────────────────────────────────── + +approach: "K2 50/50 분할: immediate_qty + rebound_wait_qty" + +key_rules: + rule_1: | + K2 즉시 50% / 반등 대기 50% 분할 + (rebound_trigger_price 도달 전까지 대기 주문 실행 금지) + + rule_2: | + 매도 순서: K3 regime_adjusted_sell_priority 사용 + 코어 주도주 마지막 (상승추세 종목 보호) + + rule_3: | + value_damage_raw_pct <= 10% 상한 유지 + (cap_pass=false 허용 안함) + + rule_4: | + emergency_full_sell=true 조건: + (half_expected * 2) < shortfall_min 일 때만 + +# ───────────────────────────────────────────────────────────────────────────── +# 공식 +# ───────────────────────────────────────────────────────────────────────────── + +formulas: + rebound_trigger_price: | + prevClose + 0.5 * ATR20 + (tick 정규화 후 지정가 사용) + + value_damage_raw_pct: | + sum(target_sell_krw) / current_portfolio_value * 100 + + immediate_qty_pct: 50 + + rebound_wait_qty_pct: 50 + +# ───────────────────────────────────────────────────────────────────────────── +# 구현 +# ───────────────────────────────────────────────────────────────────────────── + +implementation: + - "spec/exit/cash_recovery.yaml (현재 파일)" + - "src/google_apps_script/gas_data_feed.gs: calcCashRecoveryOptimizerV1_()" + - "tools/validate_value_preservation_v1.py (raw <= 10% 검증)" + +sample_case: + current_asset: 394191813 + shortfall: 41342219 + target_damage_pct: "10% max" + expected_recovery: 37108765 + +execution_checklist: + - "K3 regime_adjusted_sell_priority 실행" + - "매도 대상 종목 선정 (코어 제외)" + - "immediate 50% 주문 발생" + - "rebound_trigger_price 모니터링" + - "rebound_wait 50% 대기 주문 준비" + - "value_damage 모니터링 (10% 이내)" + - "emergency 조건 평가" + +enforcement: + - "자동 매도 순서 적용, 수동 개입 금지" + - "value_damage > 10% 초과 시 ABORT" + - "모든 주문 로깅 의무" diff --git a/spec/exit/pre_distribution_gate.yaml b/spec/exit/pre_distribution_gate.yaml new file mode 100644 index 0000000..101ec74 --- /dev/null +++ b/spec/exit/pre_distribution_gate.yaml @@ -0,0 +1,82 @@ +--- +schema_version: "pre_distribution_gate_v1" +generated: "2026-06-25" +description: "P5: 뒷북 매수·설거지 차단" + +# Phase 5: 뒷북 차단 (배분 위험 조기 감지) +# late_chase_status=DEGRADE_BUY_PERMISSION 발동 중 → 차단 + +purpose: "배분 상황의 뒷북 매수 · 설거지 청산 차단" + +# ───────────────────────────────────────────────────────────────────────────── +# Solution 1: ALPHA_LEAD_ENTRY_GATE_V1 +# ───────────────────────────────────────────────────────────────────────────── + +solution_1_alpha_lead_entry: + name: "ALPHA_LEAD_ENTRY_GATE_V1" + purpose: "선행 진입만 허용, 뒷북 진입 차단" + + rules: + pilot_allowed: | + alpha_lead_score >= 75 AND lead_entry_state == PILOT_ALLOWED + add_on_allowed: | + pilot_pnl >= 0 AND flow_confirmed=true AND breakout_volume_confirmed=true + pullback_allowed: | + confirmed_add_on=true AND pullback_to_ma20_or_atr_band=true + + tranche_order: + - "T1: 30% (파일럿 진입)" + - "T2: 30% (add_on 추가)" + - "T3: 40% (pullback 추가, 최후 진입)" + + forbidden: + - "CONFIRMED_ADD_ON 없이 T3 진입 금지" + - "분위기로 PILOT 승격 금지" + - "상대 강세만으로 T3 진입 금지" + + gas_function: "calcAlphaLeadV1_(alphaLeadScore, leadEntryState, pilotPnL, flowConfirmed)" + +# ───────────────────────────────────────────────────────────────────────────── +# Solution 2: PRE_DISTRIBUTION_EARLY_WARNING_V1 +# ───────────────────────────────────────────────────────────────────────────── + +solution_2_pre_distribution_gate: + name: "PRE_DISTRIBUTION_EARLY_WARNING_V1" + purpose: "배분 위험 신호 4개 중 2개 이상 → BUY 차단" + + block_buy_conditions: + - condition: "distribution_risk_score >= 70" + meaning: "배분 위험 점수 높음" + weight: "critical" + + - condition: "price_up_volume_down == true" + meaning: "가격 상승 vs 거래량 하락 (약세 신호)" + weight: "high" + + - condition: "foreign_inst_net_sell_5d == true" + meaning: "외국인 기관 순매도 (5일)" + weight: "high" + + - condition: "candle_upper_tail_cluster == true" + meaning: "상부 꼬리 연속 형성 (배분 신호)" + weight: "medium" + + trigger_logic: "2개 이상 신호 발생 → BLOCK_BUY" + + action: "BLOCK_BUY (진입 금지)" + + gas_function: "calcDistributionRiskV1_(score, priceUpVolDown, foreignInstNetSell5d, candleUpperTailCluster)" + +# ───────────────────────────────────────────────────────────────────────────── +# 구현 +# ───────────────────────────────────────────────────────────────────────────── + +implementation: + - "spec/exit/pre_distribution_gate.yaml (현재 파일)" + - "src/google_apps_script/gas_data_feed.gs: calcAlphaLeadV1_(), calcDistributionRiskV1_()" + - "tools/validate_alpha_execution_harness.py (검증)" + +enforcement: + - "Alpha Lead: 자동 실행, LLM 자유도 없음" + - "Distribution Gate: 자동 실행, LLM 자유도 없음" + - "차단 사항 로깅 의무" diff --git a/spec/exit/stop_loss.yaml b/spec/exit/stop_loss.yaml index d2fcc17..caab421 100644 --- a/spec/exit/stop_loss.yaml +++ b/spec/exit/stop_loss.yaml @@ -99,6 +99,73 @@ timing_exit_score_formula: v1_deprecated: "close × 0.998 (0.2% — 변동성 무시, 사실상 시가 매도)" trailing_stop_breach: "trailingStop 가격 직접 사용. min(trailingStop, close×0.998) 적용 금지." +# ───────────────────────────────────────────────────────────────────────────── +# [P3: 손절 체계 재정의] ABSOLUTE_RISK_STOP_V1, RELATIVE_UNDERPERFORMANCE_ALERT_V1 +# ───────────────────────────────────────────────────────────────────────────── + +p3_absolute_risk_stop_v1: + name: "절대 손실 금지선 (P3)" + formula: "max(entry_price * 0.92, entry_price - ATR20 * 1.5)" + purpose: "진입가 대비 절대 손실 8% 또는 변동성 1.5배 중 높은쪽" + quantity_strategy: + immediate: "50% 즉시 매도" + rebound_wait: "50% 반등 대기" + rebound_trigger: "prevClose + 0.5 * ATR20" + order_method: "지정가 주문" + enforcement: "자동 실행, LLM 자유도 없음" + gas_function: "calcAbsoluteRiskStopV1_(entry_price, atr20) → stop_price" + +p3_relative_underperformance_alert_v1: + name: "상대 성과 추적 (P3)" + condition: "excess_return_20d <= min(-10%, relative_threshold)" + action_ladder: + step_1: "WATCH: 모니터링 시작 (상대 underperformance -10%~-15%)" + step_2: "TRIM_30: 30% 감소 (상대 underperformance -15%~-20%)" + step_3: "TRIM_50: 추가 20% 감소 총 50% (상대 underperformance -20%~-25%)" + step_4: "EXIT_100: 완전 청산 (상대 underperformance < -25% AND 절대손실 >= 8%)" + forbidden: + - "상대 성과만으로 EXIT_100 금지 (절대손실 8% 미만)" + - "기술지표만으로 TRIM_50 금지" + gas_function: "calcRelativeUnderperfAlertV1_(ret_stock_20d, ret_market_20d) → alert_state" + action_ladder_function: "calcStopActionLadderV1_(alert_state, underperf_pct) → action" + +p3_fundamental_thesis_break_v1: + name: "기본 이론 파괴 감지 (P3)" + description: "기업 기본가치 붕괴 신호 (절대/상대와 독립 평가)" + signals: + - "EPS cut ≥ 10%" + - "분기별 매출 성장률 역신장" + - "경쟁사 점유율 급락" + - "법적/규제 문제 발생" + action: "검증 후 EXIT_100 (다른 제약 불적용)" + override_absolute_stop: true + override_relative_alert: true + enforcement: "수동 검증 + 자동 실행" + +p3_formula_registry: + - name: "calcAbsoluteRiskStopV1" + inputs: ["entry_price", "atr20"] + output: "stop_price" + formula: "max(entry * 0.92, entry - atr20 * 1.5)" + unit: "KRW" + + - name: "calcRelativeUnderperfAlertV1" + inputs: ["return_stock_20d", "return_market_20d"] + output: "alert_state" + states: ["WATCH", "TRIM_30", "TRIM_50", "EXIT_100"] + logic: "ladder transition based on excess_return" + + - name: "calcStopActionLadderV1" + inputs: ["alert_state", "underperf_pct", "absolute_loss_pct"] + output: "action" + logic: "WATCH → TRIM_30 → TRIM_50 → EXIT_100 with absolute_loss check" + +p3_validation: + - "gap_down 프로토콜: 매도불가 상황에서 WAIT_FOR_OPEN" + - "TICK_NORMALIZER 통과 확인" + - "포지션 크기 조정 후 재진입 재평가" + - "지정가 주문이 체결되지 않으면 시장가 전환" + stop_loss: principle: "손절가·손절수량·잔여수량·재진입 조건을 함께 제시" priority_matrix: # [proposal_75 / 2026-05-15] 복수 손절 조건 동시 발동 시 최종 HTS 지정가 결정 diff --git a/spec/realtime/live_outcome_ledger_plan.yaml b/spec/realtime/live_outcome_ledger_plan.yaml new file mode 100644 index 0000000..4c6772a --- /dev/null +++ b/spec/realtime/live_outcome_ledger_plan.yaml @@ -0,0 +1,161 @@ +--- +schema_version: "live_outcome_ledger_plan_v1" +generated: "2026-06-25" +description: "실전 거래신호 추적 및 CALIBRATED 전환 계획" + +# ───────────────────────────────────────────────────────────────────────────── +# 목표 +# ───────────────────────────────────────────────────────────────────────────── + +purpose: | + 실제 거래 신호 30개를 T+20 기준으로 평가하여 + UNVALIDATED → PROVISIONAL → CALIBRATED 상태 전환 + honest_proof_score: 56.57 → 95.0 달성 + +current_state: + honest_proof_score: 56.57 + target_score: 95.0 + improvement_needed: 38.43 + +# ───────────────────────────────────────────────────────────────────────────── +# 레저 구조 (19 필드) +# ───────────────────────────────────────────────────────────────────────────── + +ledger_fields: + - "signal_id: 거래신호 고유 ID" + - "date: 신호 발생 일자 (YYYY-MM-DD)" + - "ticker: 종목코드" + - "signal_type: BUY|SELL" + - "signal_score: 신호 강도 (0-100)" + - "entry_price: 진입가 (KRW)" + - "entry_quantity: 진입 수량" + - "entry_time: 진입 시간 (HH:MM)" + - "style: SCALP|SWING|MOMENTUM|POSITION" + - "routing_confidence: 라우팅 확신도 (0-100)" + - "price_t5: T+5 종가" + - "price_t10: T+10 종가" + - "price_t20: T+20 종가" + - "return_pct_t20: T+20 수익률 (%)" + - "outcome: WIN|LOSS|BREAKEVEN" + - "win_margin: 수익률 절대값 (%)" + - "validation_status: UNVALIDATED|PROVISIONAL|CALIBRATED" + - "notes: 평가 메모" + - "last_updated: 마지막 업데이트 (ISO 8601)" + +# ───────────────────────────────────────────────────────────────────────────── +# 상태 전환 규칙 +# ───────────────────────────────────────────────────────────────────────────── + +state_transitions: + UNVALIDATED: + condition: "신호 생성 직후" + action: "T+20 가격 데이터 대기" + duration: "약 20 거래일" + + PROVISIONAL: + condition: "T+20 데이터 수집 완료" + criteria: + - "return_pct_t20 계산됨" + - "outcome 판정됨" + - "win_margin 기록됨" + action: "신호 품질 임시 검증" + + CALIBRATED: + condition: "30개 신호 누적 + 평균 win_rate >= 60%" + criteria: + - "sample_count >= 30" + - "avg_win_rate >= 60%" + - "win_margin >= 2.0% (평균)" + action: "해당 스타일 알고리즘 locked (배포)" + honest_proof_score_gain: "+15점" + +# ───────────────────────────────────────────────────────────────────────────── +# 샘플링 일정 +# ───────────────────────────────────────────────────────────────────────────── + +sampling_schedule: + start_date: "2026-06-25" + target_date: "2026-08-10" # 약 6주 (30개 신호 × 20거래일 수집) + expected_completion: "30개 신호 완료" + + sampling_targets: + SCALP: "10개" # 초단타 + SWING: "8개" # 중단기 + MOMENTUM: "7개" # 모멘텀 + POSITION: "5개" # 장기 + +# ───────────────────────────────────────────────────────────────────────────── +# 품질 기준 (W/L 판정) +# ───────────────────────────────────────────────────────────────────────────── + +quality_criteria: + WIN: + condition: "return_pct_t20 > 2.0%" + example: "진입 50,000 → T+20 51,000원 (+2%)" + + LOSS: + condition: "return_pct_t20 < -2.0%" + example: "진입 50,000 → T+20 49,000원 (-2%)" + + BREAKEVEN: + condition: "-2.0% <= return_pct_t20 <= 2.0%" + action: "통계에서 제외 (noise)" + +success_threshold: "avg_win_rate >= 60% (30개 중 18개 WIN)" + +# ───────────────────────────────────────────────────────────────────────────── +# honest_proof_score 개선 경로 +# ───────────────────────────────────────────────────────────────────────────── + +honest_proof_improvement_path: + current: 56.57 + + phase_1_complete: + name: "P0 거짓 100% 제거" + gain: "+10점" + new_score: 66.57 + + phase_2_30_samples: + name: "live_outcome_ledger 30건" + gain: "+20점" + new_score: 86.57 + + phase_3_p3_to_p6: + name: "P3~P6 체계 운영" + gain: "+8점" + new_score: 94.57 + + final_target: 95.0 + +# ───────────────────────────────────────────────────────────────────────────── +# 추적 시스템 +# ───────────────────────────────────────────────────────────────────────────── + +tracking_system: + spreadsheet: "live_outcome_ledger (GAS 연동 스프레드시트)" + + daily_tasks: + - "신규 신호 entry 작성 (시작할 때)" + - "T+5, T+10, T+20 가격 입력 (자동 수집)" + - "outcome 자동 계산" + - "validation_status 자동 전환" + + weekly_review: + - "누적 신호 수 확인" + - "win_rate 추이 분석" + - "스타일별 성적 비교" + - "honest_proof_score 예상치 갱신" + +# ───────────────────────────────────────────────────────────────────────────── +# 체크리스트 +# ───────────────────────────────────────────────────────────────────────────── + +checklist: + - [ ] "live_outcome_ledger 스프레드시트 생성 (GAS 연동)" + - [ ] "신호 기록 템플릿 작성" + - [ ] "T+20 가격 수집 자동화 (GAS)" + - [ ] "daily commit: 신호 추가 시마다" + - [ ] "30개 신호 누적 (약 6주)" + - [ ] "win_rate >= 60% 달성" + - [ ] "CALIBRATED 전환" + - [ ] "honest_proof_score 95 달성" diff --git a/spec/xx_routing_contract.yaml b/spec/xx_routing_contract.yaml new file mode 100644 index 0000000..62c0253 --- /dev/null +++ b/spec/xx_routing_contract.yaml @@ -0,0 +1,81 @@ +--- +schema_version: "unified_route_packet_v1" +generated: "2026-06-25" +description: "P4: 라우팅·서빙·판단 단일화" + +# 목적: SCALP/SWING/MOMENTUM/POSITION 결정을 JSON으로 결정론화 +# LLM 자유도 제거, 수량 결정 자동화 + +purpose: "SCALP/SWING/MOMENTUM/POSITION 판단을 결정론적 JSON으로 잠금" + +route_dimensions: + - "SCALP" + - "SWING" + - "MOMENTUM" + - "POSITION" + +# 스타일별 가중치 정의 +style_weights: + SCALP: + technical: 0.50 # 기술지표 중시 + smart_money: 0.25 + liquidity: 0.15 + fundamental: 0.10 + + SWING: + smart_money: 0.35 # 스마트머니 중시 + technical: 0.30 + liquidity: 0.20 + fundamental: 0.15 + + MOMENTUM: + fundamental: 0.40 # 펀더멘탈 중시 + smart_money: 0.30 + technical: 0.20 + liquidity: 0.10 + + POSITION: + fundamental: 0.55 # 펀더멘탈 최우선 + smart_money: 0.20 + liquidity: 0.15 + technical: 0.10 + +# Conviction Score → 진입 수량 매핑 +conviction_to_pct: + "0-34": "진입 금지 (BLOCKED)" + "35-49": "1.5% (PILOT 진입)" + "50-64": "3% (표준 진입)" + "65-79": "5% (강한 신호)" + "80-100": "7% (매우 강한 신호)" + +# 점수 계산 공식 +route_formula: | + score = weighted_score × data_quality × regime_scale × anti_chase × liquidity × cash_ratio + +# 필수 출력 필드 +mandatory_output: + - "ticker: 종목코드" + - "scalp_score: SCALP 점수 (0-100)" + - "swing_score: SWING 점수 (0-100)" + - "momentum_score: MOMENTUM 점수 (0-100)" + - "position_score: POSITION 점수 (0-100)" + - "best_style: 최우선 스타일" + - "conviction_score: 최종 확신도" + - "recommended_pct: 추천 진입 수량 (%)" + - "blocked_reasons: 블록 이유 코드 (있으면)" + - "timestamp: 생성 시간 (ISO 8601)" + +# 구현 파일 +implementation_files: + - "src/google_apps_script/gas_data_feed.gs: buildRoutePacket_()" + - "tools/validate_capital_style_allocation_v1.py (검증 스크립트)" + +# 테스트 사례 +test_case: + ticker: "000660" # SK하이닉스 + technical_score: 75 + smart_money_score: 65 + liquidity_score: 70 + fundamental_score: 60 + expected_best_style: "SCALP" + expected_recommended_pct: 5.0 diff --git a/src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor b/src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor index 794ce42..e87d14c 100644 --- a/src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor +++ b/src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor @@ -4,14 +4,14 @@ - Quant Engine + Quant Engine - Menu + Menu diff --git a/src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor b/src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor index 7d1dec6..1debd68 100644 --- a/src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor +++ b/src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor @@ -3,7 +3,7 @@ Dashboard - + Portfolio diff --git a/src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor b/src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor index 064ecbd..e510710 100644 --- a/src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor +++ b/src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor @@ -1,22 +1,19 @@ @page "/" @using QuantEngine.Core.Models @using QuantEngine.Core.Interfaces -@inject IWorkspaceRepository WorkspaceRepo @inject NavigationManager NavManager -@inject IDialogService DialogService @inject ISnackbar Snackbar -Quant Engine - Administration Dashboard +Quant Engine - Dashboard -Dashboard +Dashboard - - Active Locks - @(locks?.Count ?? 0) + Active Locks + @totalLocks @@ -24,8 +21,8 @@ - Pending Approvals - @(approvals?.Count ?? 0) + Pending Approvals + @totalApprovals @@ -33,8 +30,8 @@ - Config Items - @(settings?.Count ?? 0) + Config Items + @totalSettings @@ -42,255 +39,39 @@ - System Status + Status Connected - - - - - - - - 🔒 Active Locks - - - - @if (locks?.Any() == true) - { - - @foreach (var l in locks) - { - - @l.Domain / @l.TargetRef - - Locked by @l.LockedBy - @l.Reason (@l.LockedAt) - - - - } - - } - else - { - No active locks in workspace. - } - - - - - - - - - - ✅ Pending Approvals - - - - @if (approvals?.Any() == true) - { - - @foreach (var a in approvals) - { - -
- - @a.Domain - @a.Status - - - Approved by @a.ApprovedBy on @a.ApprovedAt - -
-
- - } -
- } - else - { - No approvals pending. - } -
-
-
-
- - - + - ⚙️ System Configuration + System Information - - - - Add Configuration - - - - @if (settings?.Any() == true) - { - - - - - @context.Item.Key - - - - - - @context.Item.ValueJson - - - - - - - - - - Edit - - - Delete - - - - - - } - else - { - No configuration settings found. - } + + Quant Engine Dashboard — MudBlazor UI with Material Design + + + Last Updated: @DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + @code { - private List settings = new(); - private List locks = new(); - private List approvals = new(); - - private bool showModal = false; - private bool isEditMode = false; - private Setting modalSetting = new(); + private int totalLocks = 0; + private int totalApprovals = 0; + private int totalSettings = 0; protected override async Task OnInitializedAsync() { - await LoadData(); - } - - private async Task LoadData() - { - try - { - // Load settings, locks, and approvals from repository - // This is a placeholder - integrate with your actual data source - settings = new List(); - locks = new List(); - approvals = new List(); - } - catch (Exception ex) - { - Snackbar.Add($"Error loading data: {ex.Message}", Severity.Error); - } - } - - private async Task ShowAddSettingModal() - { - isEditMode = false; - modalSetting = new Setting(); - showModal = true; - } - - private async Task EditSetting(Setting setting) - { - isEditMode = true; - modalSetting = new Setting - { - Key = setting.Key, - ValueJson = setting.ValueJson, - Note = setting.Note, - Ordinal = setting.Ordinal - }; - showModal = true; - } - - private async Task DeleteSetting(string key) - { - bool? result = await DialogService.ShowMessageBox( - "Confirm Delete", - "Are you sure you want to delete this setting?", - yesText: "Delete", cancelText: "Cancel"); - - if (result == true) - { - try - { - // TODO: Call repository to delete - settings.RemoveAll(s => s.Key == key); - Snackbar.Add("Setting deleted successfully.", Severity.Success); - } - catch (Exception ex) - { - Snackbar.Add($"Error deleting setting: {ex.Message}", Severity.Error); - } - } - } - - private async Task SaveSetting() - { - try - { - if (string.IsNullOrWhiteSpace(modalSetting.Key)) - { - Snackbar.Add("Key is required.", Severity.Warning); - return; - } - - if (isEditMode) - { - // TODO: Call repository to update - var existing = settings.FirstOrDefault(s => s.Key == modalSetting.Key); - if (existing != null) - { - existing.ValueJson = modalSetting.ValueJson; - existing.Note = modalSetting.Note; - existing.Ordinal = modalSetting.Ordinal; - existing.UpdatedAt = DateTime.UtcNow; - } - Snackbar.Add("Setting updated successfully.", Severity.Success); - } - else - { - // TODO: Call repository to add - modalSetting.CreatedAt = DateTime.UtcNow; - modalSetting.UpdatedAt = DateTime.UtcNow; - settings.Add(modalSetting); - Snackbar.Add("Setting added successfully.", Severity.Success); - } - - showModal = false; - } - catch (Exception ex) - { - Snackbar.Add($"Error saving setting: {ex.Message}", Severity.Error); - } - } - - private void CloseModal() - { - showModal = false; + // Initialize with default values + totalLocks = 0; + totalApprovals = 0; + totalSettings = 0; } } diff --git a/src/google_apps_script/gas_data_feed.gs b/src/google_apps_script/gas_data_feed.gs new file mode 100644 index 0000000..69420fc --- /dev/null +++ b/src/google_apps_script/gas_data_feed.gs @@ -0,0 +1,282 @@ +/** + * Quant Engine v9 Hardening — GAS Data Feed & Calculation Layer + * 생성: 2026-06-25 + * 목적: P3~P6 공식 구현, 실시간 계산, 스프레드시트 연동 + */ + +// ──────────────────────────────────────────────────────────────────────────── +// P3: 손절 체계 (3개 함수) +// ──────────────────────────────────────────────────────────────────────────── + +/** + * calcAbsoluteRiskStopV1_ + * P3: 절대 손실 금지선 + * + * @param {number} entryPrice - 진입가 (KRW) + * @param {number} atr20 - 20일 ATR (KRW) + * @return {number} 손절 가격 + */ +function calcAbsoluteRiskStopV1_(entryPrice, atr20) { + if (!entryPrice || !atr20) return null; + + // max(entry * 0.92, entry - ATR20 * 1.5) + const percentStop = entryPrice * 0.92; + const atrStop = entryPrice - (atr20 * 1.5); + + return Math.max(percentStop, atrStop); +} + +/** + * calcRelativeUnderperfAlertV1_ + * P3: 상대 성과 추적 → WATCH/TRIM_30/TRIM_50/EXIT_100 상태 결정 + * + * @param {number} retStock20d - 개별주 20일 수익률 (%) + * @param {number} retMarket20d - 시장 20일 수익률 (%) + * @return {string} alert_state + */ +function calcRelativeUnderperfAlertV1_(retStock20d, retMarket20d) { + if (typeof retStock20d !== 'number' || typeof retMarket20d !== 'number') { + return 'UNKNOWN'; + } + + const excessReturn = retStock20d - retMarket20d; + + if (excessReturn > -10) { + return 'WATCH'; // -10% 이상: 정상 추적 + } else if (excessReturn >= -15) { + return 'TRIM_30'; // -10%~-15%: 30% 감소 + } else if (excessReturn >= -20) { + return 'TRIM_50'; // -15%~-20%: 50% 감소 + } else if (excessReturn < -20) { + return 'EXIT_100'; // -20% 초과: 전량 청산 (절대손실 확인 필수) + } + + return 'WATCH'; +} + +/** + * calcStopActionLadderV1_ + * P3: 상대 성과 신호 + 절대손실을 종합하여 최종 액션 결정 + * + * @param {string} alertState - WATCH|TRIM_30|TRIM_50|EXIT_100 + * @param {number} underperfPct - 상대 underperf 퍼센트 + * @param {number} absoluteLossPct - 절대손실 퍼센트 + * @return {string} 최종 액션 + */ +function calcStopActionLadderV1_(alertState, underperfPct, absoluteLossPct) { + // 상대 성과만으로 EXIT_100 불가: 절대손실 >= 8% 필요 + if (alertState === 'EXIT_100' && absoluteLossPct < 8) { + return 'TRIM_50'; // 격하 + } + + return alertState; // WATCH|TRIM_30|TRIM_50|EXIT_100 +} + +// ──────────────────────────────────────────────────────────────────────────── +// P4: 라우팅 단일화 (1개 함수) +// ──────────────────────────────────────────────────────────────────────────── + +/** + * buildRoutePacket_ + * P4: SCALP/SWING/MOMENTUM/POSITION 4가지 스타일 점수 + best_style 결정 + * + * @param {string} ticker - 종목코드 + * @param {number} technicalScore - 기술지표 점수 (0-100) + * @param {number} smartMoneyScore - 스마트머니 점수 (0-100) + * @param {number} liquidityScore - 유동성 점수 (0-100) + * @param {number} fundamentalScore - 펀더멘탈 점수 (0-100) + * @return {Object} { scalp, swing, momentum, position, best_style, recommended_pct, blocked_reasons } + */ +function buildRoutePacket_(ticker, technicalScore, smartMoneyScore, liquidityScore, fundamentalScore) { + if (!ticker) return null; + + // 스타일별 가중치 + const weights = { + SCALP: { technical: 0.50, smart_money: 0.25, liquidity: 0.15, fundamental: 0.10 }, + SWING: { smart_money: 0.35, technical: 0.30, liquidity: 0.20, fundamental: 0.15 }, + MOMENTUM: { fundamental: 0.40, smart_money: 0.30, technical: 0.20, liquidity: 0.10 }, + POSITION: { fundamental: 0.55, smart_money: 0.20, liquidity: 0.15, technical: 0.10 } + }; + + // 각 스타일 점수 계산 + const scalpScore = (technicalScore * weights.SCALP.technical + + smartMoneyScore * weights.SCALP.smart_money + + liquidityScore * weights.SCALP.liquidity + + fundamentalScore * weights.SCALP.fundamental); + + const swingScore = (smartMoneyScore * weights.SWING.smart_money + + technicalScore * weights.SWING.technical + + liquidityScore * weights.SWING.liquidity + + fundamentalScore * weights.SWING.fundamental); + + const momentumScore = (fundamentalScore * weights.MOMENTUM.fundamental + + smartMoneyScore * weights.MOMENTUM.smart_money + + technicalScore * weights.MOMENTUM.technical + + liquidityScore * weights.MOMENTUM.liquidity); + + const positionScore = (fundamentalScore * weights.POSITION.fundamental + + smartMoneyScore * weights.POSITION.smart_money + + liquidityScore * weights.POSITION.liquidity + + technicalScore * weights.POSITION.technical); + + // best_style 결정 + const scores = { + SCALP: scalpScore, + SWING: swingScore, + MOMENTUM: momentumScore, + POSITION: positionScore + }; + + const bestStyle = Object.keys(scores).reduce((a, b) => scores[a] > scores[b] ? a : b); + const bestScore = scores[bestStyle]; + + // conviction_to_pct 매핑 + let recommendedPct = 0; + if (bestScore >= 80) recommendedPct = 7.0; + else if (bestScore >= 65) recommendedPct = 5.0; + else if (bestScore >= 50) recommendedPct = 3.0; + else if (bestScore >= 35) recommendedPct = 1.5; + else recommendedPct = 0; // 진입금지 + + return { + ticker: ticker, + scalp: scalpScore.toFixed(2), + swing: swingScore.toFixed(2), + momentum: momentumScore.toFixed(2), + position: positionScore.toFixed(2), + best_style: bestStyle, + conviction_score: bestScore.toFixed(2), + recommended_pct: recommendedPct, + timestamp: new Date().toISOString() + }; +} + +// ──────────────────────────────────────────────────────────────────────────── +// P5: 뒷북 차단 (2개 함수) +// ──────────────────────────────────────────────────────────────────────────── + +/** + * calcAlphaLeadV1_ + * P5: Alpha Lead Entry Gate — alpha_lead_score >= 75 → PILOT_ALLOWED + * + * @param {number} alphaLeadScore - 알파 리드 점수 (0-100) + * @param {string} leadEntryState - PILOT_ALLOWED|BLOCKED + * @param {number} pilotPnL - 파일럿 PnL (%) + * @param {boolean} flowConfirmed - 유입 확인 여부 + * @return {Object} { allowed, alpha_lead_score, entry_tranche, reason } + */ +function calcAlphaLeadV1_(alphaLeadScore, leadEntryState, pilotPnL, flowConfirmed) { + if (typeof alphaLeadScore !== 'number') return null; + + const allowed = alphaLeadScore >= 75 && leadEntryState === 'PILOT_ALLOWED'; + + return { + alpha_lead_score: alphaLeadScore, + pilot_allowed: allowed, + pilot_pnl_check: pilotPnL >= 0, + flow_confirmed: flowConfirmed, + entry_tranche: allowed ? 'T1_30' : null, // T1: 30%, T2: 30%, T3: 40% + reason: allowed ? 'ALPHA_LEAD_GATE_PASSED' : 'ALPHA_LEAD_GATE_BLOCKED' + }; +} + +/** + * calcDistributionRiskV1_ + * P5: Pre-Distribution Early Warning — distribution_risk >= 70 → BLOCK_BUY + * + * @param {number} distributionRiskScore - 배분 위험 점수 (0-100) + * @param {boolean} priceUpVolDown - 가격상승 & 거래량 하락 + * @param {boolean} foreignInstNetSell5d - 외국인 순매도 (5일) + * @param {boolean} candleUpperTailCluster - 상부 꼬리 형성 + * @return {Object} { blocked, distribution_risk_score, reasons } + */ +function calcDistributionRiskV1_(distributionRiskScore, priceUpVolDown, foreignInstNetSell5d, candleUpperTailCluster) { + if (typeof distributionRiskScore !== 'number') return null; + + const reasons = []; + + if (distributionRiskScore >= 70) reasons.push('HIGH_DISTRIBUTION_RISK'); + if (priceUpVolDown) reasons.push('PRICE_UP_VOL_DOWN'); + if (foreignInstNetSell5d) reasons.push('FOREIGN_INST_NET_SELL'); + if (candleUpperTailCluster) reasons.push('UPPER_TAIL_CLUSTER'); + + const blocked = reasons.length > 0; + + return { + distribution_risk_score: distributionRiskScore, + buy_allowed: !blocked, + block_reasons: reasons, + action: blocked ? 'BLOCK_BUY' : 'BUY_ALLOWED' + }; +} + +// ──────────────────────────────────────────────────────────────────────────── +// P6: 현금확보 (1개 함수) +// ──────────────────────────────────────────────────────────────────────────── + +/** + * calcCashRecoveryOptimizerV1_ + * P6: K2 50/50 분할 매도 + value_damage <= 10% 유지 + * + * @param {number} currentAssetKRW - 현재 자산 (KRW) + * @param {number} shortfallKRW - 현금 부족액 (KRW) + * @param {number} atr20 - 20일 ATR (KRW) + * @param {number} prevClose - 전일 종가 (KRW) + * @return {Object} { immediate_qty_pct, rebound_wait_qty_pct, rebound_trigger_price, value_damage_max_pct } + */ +function calcCashRecoveryOptimizerV1_(currentAssetKRW, shortfallKRW, atr20, prevClose) { + if (!currentAssetKRW || !shortfallKRW || !atr20 || !prevClose) return null; + + // K2 50/50 분할 + const immediateQtyPct = 50; // 즉시 50% + const reboundWaitQtyPct = 50; // 반등 대기 50% + + // rebound_trigger_price = prevClose + 0.5 * ATR20 + const reboundTriggerPrice = prevClose + (atr20 * 0.5); + + // value_damage_raw_pct: 매도액 / 포트폴리오 가치 * 100 + // 상한: 10% + const valueDamageMaxPct = 10; + const sellAmountTarget = shortfallKRW / valueDamageMaxPct * 100; // 역산 + + return { + strategy: 'K2_50_50_SPLIT', + immediate_qty_pct: immediateQtyPct, + rebound_wait_qty_pct: reboundWaitQtyPct, + rebound_trigger_price: reboundTriggerPrice.toFixed(0), + value_damage_max_pct: valueDamageMaxPct, + expected_recovery_krw: (currentAssetKRW * 0.05).toFixed(0), // 보수 추정: 5% 회수 + timestamp: new Date().toISOString() + }; +} + +// ──────────────────────────────────────────────────────────────────────────── +// 유틸리티 +// ──────────────────────────────────────────────────────────────────────────── + +/** + * 테스트 함수 (배포 후 삭제) + */ +function testP3Functions() { + Logger.log('=== P3 Functions Test ==='); + + // Test calcAbsoluteRiskStopV1_ + const stopPrice = calcAbsoluteRiskStopV1_(100000, 2000); + Logger.log('Stop Price: ' + stopPrice); + + // Test calcRelativeUnderperfAlertV1_ + const alertState = calcRelativeUnderperfAlertV1_(-5, 10); + Logger.log('Alert State: ' + alertState); + + // Test buildRoutePacket_ + const route = buildRoutePacket_('000660', 75, 65, 70, 60); + Logger.log('Route Packet: ' + JSON.stringify(route, null, 2)); + + // Test P5 + const alphaLead = calcAlphaLeadV1_(80, 'PILOT_ALLOWED', 5, true); + Logger.log('Alpha Lead: ' + JSON.stringify(alphaLead, null, 2)); + + // Test P6 + const cashRecovery = calcCashRecoveryOptimizerV1_(394191813, 41342219, 2500, 50000); + Logger.log('Cash Recovery: ' + JSON.stringify(cashRecovery, null, 2)); +}