diff --git a/docs/superpowers/specs/2026-05-13-screener-node-canvas-design.md b/docs/superpowers/specs/2026-05-13-screener-node-canvas-design.md new file mode 100644 index 0000000..896f51d --- /dev/null +++ b/docs/superpowers/specs/2026-05-13-screener-node-canvas-design.md @@ -0,0 +1,505 @@ +# Stock Screener — Node Canvas Mode Design + +**작성일**: 2026-05-13 +**작성자**: gahusb +**상태**: Approved for implementation +**선행 spec**: `2026-05-12-stock-screener-board-design.md` (§14 — react-flow 노드 캔버스 후속 슬라이스) + +--- + +## 1. 목표 + +`/stock/screener` 페이지에 **n8n 스타일 노드 캔버스 모드**를 추가한다. 폼 모드와 토글로 전환하며, 같은 settings state를 공유한다. 백엔드는 변경하지 않는다 — 캔버스는 시각화 + 편집 UI일 뿐, 결과적으로는 동일한 `weights / node_params / gate_params` 를 `/api/stock/screener/run` 에 전송한다. + +**Why**: 사용자가 슬라이더만 들여다보는 폼 모드는 "어떤 노드가 어떤 단계에서 무엇을 하는지"의 파이프라인 감각이 약하다. n8n/Figma류 캔버스 시각화는 데이터 흐름을 한눈에 보여줘 강세주 분석 모델의 구조적 이해를 돕는다. + +--- + +## 2. 범위 + +**포함 (이번 슬라이스)**: +- 헤더 토글 (`폼 ↔ 캔버스`) — 데스크탑 전용 +- 11개 노드의 미니 파이프라인 시각화 (고정 토폴로지) +- 점수 노드 카드 위 가중치/활성/핵심 파라미터 인라인 편집 + 설명 표시 +- floating 미니 툴바 (실행 / 저장 실행 / 설정 영구 저장 / 레이아웃 리셋) +- 노드 위치 localStorage 저장 + 초기화 버튼 +- 모바일에서는 캔버스 토글 숨김, 폼 강제 + +**범위 외 (NOT)**: +- 노드 추가/삭제 UI (토폴로지 고정) +- 노드 간 연결선 사용자 편집 +- 자유 그래프 모드 (별도 후속 슬라이스) +- 캔버스 안 결과 노드에 결과 표시 (외부 테이블에만 표시) +- 노드 캔버스 화면 자체에서의 대화형 백테스트 +- dagre 등 자동 레이아웃 알고리즘 + +--- + +## 3. 아키텍처 개요 + +``` + ┌─────────────────────────────┐ + │ Screener.jsx (entrypoint) │ + │ - useScreenerMode (form|canvas) │ + │ - useIsMobile() → 강제 form │ + └────────────┬────────────────┘ + │ + ┌────────────────┼────────────────┐ + │ │ │ + form mode canvas mode shared result area + (기존 그대로) (신규) (기존 그대로) + │ │ │ + ┌──────────┴──┐ ┌─────────┴──────┐ ┌────┴──────┐ + │ GatePanel │ │ ScreenerCanvas │ │ ResultTable + │ NodePanel │ │ + CanvasToolbar│ │ TelegramPreview + │ GlobalControls│ │ + Node cards │ │ RunHistoryList + └──────────────┘ └─────────────────┘ └───────────┘ + ↑ ↑ ↑ + └────────────────┴────────────────┘ + 공유 state: useScreenerSettings, + useScreenerRun, useScreenerHistory +``` + +**의존성 추가**: `@xyflow/react` (구 react-flow, MIT, ~50KB gzipped). + +**백엔드 변경 없음**. 캔버스는 settings를 동일한 형태로 만들고, 동일한 `/run` 엔드포인트를 호출한다. + +--- + +## 4. 화면 레이아웃 + +### 4.1 데스크탑 — 캔버스 모드 + +``` +┌───────────────────────────────────────────────────────────┐ +│ Header: 스크리너 [폼] [캔버스] │ +│ 최근 자동잡: 2026-05-13 · 분석 기준일: 2026-05-13│ +├───────────────────────────────────────────────────────────┤ +│ ╔═════════════════════════════════════════════════════╗ │ +│ ║ ┌─ floating toolbar ──────────────────────────┐ ║ │ +│ ║ │ ▶ 실행 💾 저장 실행 📌 설정 저장 🔄 ⛶ │ ║ │ +│ ║ └──────────────────────────────────────────────┘ ║ │ +│ ║ ║ │ +│ ║ ┌─────┐ ┌──────┐ ┌───────┐ ║ │ +│ ║ │📥KRX│→ │🛡️위생│ ┬→│외국인 │ ┐ ║ │ +│ ║ │data │ │gate │ ├→│거래량 │ │ ┌─────────────┐ ║ │ +│ ║ └─────┘ └──────┘ ├→│모멘텀 │ ┼→ │⚙️가중합+TopN │→ │📊│║│ +│ ║ ├→│52w고가│ │ │ +ATR 사이저 │ ║ │ +│ ║ ├→│RS │ │ └─────────────┘ ║ │ +│ ║ ├→│이평선│ ┤ ║ │ +│ ║ └→│VCP │ ┘ ║ │ +│ ║ ║ │ +│ ║ (캔버스 영역: 화면 높이의 약 60-65%) ║ │ +│ ╚═══════════════════════════════════════════════════════╝ │ +├───────────────────────────────────────────────────────────┤ +│ ResultTable (기존 그대로) — 비교 모드 그대로 │ +│ TelegramPreview (기존 그대로) │ +│ RunHistoryList (기존 그대로 — 우측 사이드) │ +└───────────────────────────────────────────────────────────┘ +``` + +**그리드 구성 (캔버스 모드)**: + +- Row 1 — 헤더 (높이 자동) +- Row 2 — 캔버스 영역 (`min-height: 60vh`, `max-height: 70vh`) +- Row 3 — 2-column: 좌측 `ResultTable + TelegramPreview` (flex 1), 우측 `RunHistoryList` (width 300px) + +폼 모드의 3-column 그리드(좌 사이드/센터/우 사이드)와 달리, 캔버스 모드는 캔버스가 가로 전체를 쓰고 결과 영역만 2-column으로 분리. `RunHistoryList` 의 위치는 두 모드 모두 "우측 결과 사이드"로 일관. + +### 4.2 데스크탑 — 폼 모드 + +기존 layout 그대로. 헤더에 토글 [폼] [캔버스]만 추가. + +### 4.3 모바일 (<768px) + +기존 모바일 카드 layout 그대로. 헤더 토글 자체를 렌더하지 않음. localStorage에 `mode='canvas'`로 저장돼 있어도 무시. + +--- + +## 5. 노드 종류 + +총 11개 노드, 4개 카테고리. + +| 카테고리 | 노드 | 편집 | 색상 | 표시 정보 | +|----------|------|------|------|-----------| +| **데이터** | `📥 KRX 데이터` | 불가 | 회색 | "~2,800종목 · FDR" | +| **게이트** | `🛡️ 위생 게이트` | 가능 | 노랑 | 파라미터 (min_market_cap 등) + 활성/비활성 | +| **점수** | `📈 외국인` | 가능 | 컬러 | 가중치 + 핵심 파라미터 + 설명 | +| **점수** | `📊 거래량 급증` | 가능 | 컬러 | 동일 | +| **점수** | `🚀 모멘텀` | 가능 | 컬러 | 동일 | +| **점수** | `🔝 52w 고가` | 가능 | 컬러 | 동일 | +| **점수** | `💪 RS Rating` | 가능 | 컬러 | 동일 | +| **점수** | `📉 이평선 정렬` | 가능 | 컬러 | 동일 | +| **점수** | `🌀 VCP-lite` | 가능 | 컬러 | 동일 | +| **결합** | `⚙️ 가중합+TopN+ATR` | 불가 | 회색 | "TopN=10 · ATR×2" 등 현재 settings 요약 | +| **결과** | `📊 결과` | 불가 | 회색 | "마지막 실행: 2026-05-13 · 8종목 통과" | + +점수 노드의 컬러는 기존 `NODE_META` 의 accent color 시스템과 동기화 — 폼 모드에서 쓰던 색상이 캔버스에서도 동일하게 적용. + +--- + +## 6. 노드 카드 디자인 + +### 6.1 점수 노드 카드 (편집 가능) + +``` +┌──────────────────────────────────┐ +│ 📈 거래량 급증 ⓘ │ ← 호버 시 풀 설명 툴팁 +│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ +│ "20일 평균 대비 2배 이상" │ ← 항상 표시되는 한 줄 요약 +│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ +│ 가중치 [█████░░░░░] 0.5 │ ← 슬라이더 (0~1, step 0.05) +│ ☑ 활성 │ ← 체크박스. uncheck = weight 0 +│ │ +│ ▾ 파라미터 (펼치면) │ +│ lookback_days: [ 20 ] 일 │ +│ multiplier: [2.0 ] │ +└──────────────────────────────────┘ +``` + +- 한 줄 요약: 기존 `NODE_META[name].summary` (없으면 `description` 첫 줄) +- 풀 설명 (호버 툴팁): 기존 `NODE_META[name].description` +- 파라미터 폼: `param_schema` 기반 자동 생성 (기존 `NodeCard.jsx` 와 동일 로직 재사용) + +### 6.2 게이트 노드 카드 (편집 가능, 노랑) + +``` +┌──────────────────────────────────┐ +│ 🛡️ 위생 게이트 ⓘ │ +│ "통과해야 점수 단계 진입" │ +│ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ +│ ☑ 활성 │ +│ ▾ 파라미터 │ +│ min_market_cap: [50] 억원 │ +│ exclude_spac: ☑ │ +│ ... │ +└──────────────────────────────────┘ +``` + +### 6.3 고정 노드 카드 (정보 표시만, 회색) + +``` +┌────────────────────┐ +│ 📥 KRX 데이터 │ +│ ~2,800종목 · FDR │ +└────────────────────┘ +``` + +결합 노드는 동적으로 현재 settings를 요약 표시: +``` +┌────────────────────────────┐ +│ ⚙️ 가중합 + TopN + ATR │ +│ Top 10 · RR 2.0 · ATR×2 │ ← settings에서 계산해서 표시 +└────────────────────────────┘ +``` + +결과 노드도 동적: +``` +┌──────────────────────────┐ +│ 📊 결과 │ +│ 마지막 실행: 14:32 │ +│ 8 / 12 종목 통과 │ +└──────────────────────────┘ +``` + +--- + +## 7. 캔버스 인터랙션 + +| 동작 | 결과 | +|------|------| +| 노드 드래그 | 위치 변경 → 드래그 종료 시 `screener-canvas-layout-v1` localStorage에 저장 | +| 슬라이더 변경 | `useScreenerSettings.setLocal({...settings, weights: {...}})` → `dirty=true` | +| 체크박스 (활성) | weight 토글: uncheck 시 weight=0 저장, check 시 이전 값 복원 (default = 0.5) | +| 파라미터 ▾ 펼치기 | 카드 높이 동적 확장 | +| 마우스 휠 | 줌 (React Flow 기본) | +| 드래그 (빈 공간) | 팬 (React Flow 기본) | +| ⛶ fitView 버튼 | 전체 노드 화면 맞춤 | +| 🔄 레이아웃 리셋 | `INITIAL_NODE_POSITIONS` 로 복귀, localStorage 키 삭제 | +| ▶ 실행 | 기존 `runPreview(settings)` → 결과는 하단 ResultTable | +| 💾 저장 실행 | 기존 `runSave(settings)` → DB 영구화 | +| 📌 설정 저장 | 기존 `save()` (settings 영구화) | + +엣지 연결선은 사용자가 편집할 수 없음 (고정). React Flow 인스턴스 prop `nodesConnectable={false}`, `edgesUpdatable={false}`. + +--- + +## 8. 컴포넌트 분해 (신규 파일) + +``` +src/pages/stock/screener/ + Screener.jsx ← 모드 토글 추가, canvas 모드 분기 렌더 + hooks/ + useScreenerMode.js ← 신규: 'form' | 'canvas' state + localStorage + useCanvasLayout.js ← 신규: 노드 위치 read/write/reset + (기존 hooks 그대로) + components/ + ModeToggle.jsx ← 신규: [폼][캔버스] 세그먼트 컨트롤 (헤더용) + canvas/ + CanvasLayout.jsx ← 신규: 캔버스 + 결과 영역 그리드 (4.1 그리드 구성) + ScreenerCanvas.jsx ← React Flow 루트 컨테이너 + CanvasToolbar.jsx ← floating Panel (실행/저장/리셋/fitView) + nodes/ + ScoreNodeCard.jsx ← 점수 노드 카드 (편집) + GateNodeCard.jsx ← 게이트 노드 카드 (편집) + FixedNodeCard.jsx ← 데이터/결합/결과 카드 (정보만) + constants/ + canvasLayout.js ← INITIAL_NODE_POSITIONS / EDGES / NODE_KIND_MAP + (기존 components 그대로 — 폼 모드에서 계속 사용) +``` + +기존 컴포넌트(`GatePanel`, `NodePanel`, `GlobalControls`, `ResultTable`, `TelegramPreview`, `RunHistoryList`)는 **변경 없음**. 결과 영역은 모드와 무관하게 동일. + +### 8.1 `Screener.jsx` 변경점 + +```jsx +const { mode, setMode } = useScreenerMode(); +const isMobile = useIsMobile(); +const effectiveMode = isMobile ? 'form' : mode; + +return ( +