Files
web-page/docs/superpowers/specs/2026-07-03-watchlist-tab-design.md
2026-07-03 01:43:03 +09:00

8.7 KiB
Raw Blame History

관심종목 탭 (Watchlist Tab) — FE 설계

  • 작성일: 2026-07-03
  • 역할/저장소: FE (web-ui)
  • 상위 스펙(BE): web-page-backend/docs/superpowers/specs/2026-07-02-realtime-trade-alerts-design.md §2·§5.3
  • 상위 플랜(BE): web-page-backend/docs/superpowers/plans/2026-07-02-realtime-trade-alerts.md
  • 범위: FE(web-ui)만. BE 계약(§5.3)을 소비하는 "관심종목" 탭 구현. 워커(web-ai)·BE는 별도 세션.

1. 배경 & 목표

실시간 매매 알림 시스템의 매수 유니버스는 "watchlist(사용자 관리) 당일 스크리너 후보" 로 정의된다(BE 스펙 §2). 관심종목 관리 수단은 "텔레그램 봇 명령 + web-ui 탭 둘 다" 로 결정되었다. 본 문서는 그중 web-ui 탭을 정의한다.

목표:

  1. 사용자가 관심종목을 웹에서 추가/조회/삭제(CRUD)할 수 있다.
  2. 최근 발생한 매수·매도 시그널 알림 이력을 웹에서 확인할 수 있다.

비목표(YAGNI, v1 제외):

  • 종목별 조건 오버라이드(params_json: trailing_pct, stop_pct 등) 편집 — BE POST/PUT params 계약 미확정.
  • 실시간 WebSocket 알림 스트림 — 폴링/수동 새로고침으로 충분.
  • 텔레그램 설정 UI.

2. 소비할 BE 계약 (§5.3)

메서드 경로 요청 응답
GET /api/stock/watchlist { watchlist: [{ ticker, name, note, params, added_at }] }
POST /api/stock/watchlist { ticker, name?, note? } 201 { ok: true }
DELETE /api/stock/watchlist/{ticker} 200 / 404
GET /api/stock/trade-alerts?days=N { alerts: [{ id, ticker, name, kind, condition, price, detail, fired_at }] }

알림 필드 enum (BE 스펙 §5.3):

  • kind: buy | sell
  • condition (buy): buy_ma20_pullback · buy_breakout · buy_rsi_bounce
  • condition (sell): sell_stop_loss · sell_ma_break · sell_take_profit · sell_climax · sell_trailing_stop

응답 래핑 키(watchlist/alerts)와 params 필드는 BE 스펙 문구 기준. FE는 방어적으로 파싱한다(배열 직접 반환 / 래핑 둘 다 허용, params 미사용이면 무시).


3. 배치 & 탭 등재

/stock/trade (거래 데스크)에 5번째 메인 탭 "관심종목" 추가. 기존 탭 등재 패턴을 그대로 확장한다.

  • src/pages/stock/stockUtils.js: export const TAB_WATCHLIST = 'watchlist';
  • src/pages/stock/StockTrade.jsx:
    • TAB_ORDER 배열에 TAB_WATCHLIST 추가
    • tabLabels'관심종목' 추가
    • 데스크탑 탭 버튼 배열에 { id: TAB_WATCHLIST, icon: '⭐', label: '관심종목', sub: '관리·시그널', badge: <count> } 추가
    • 모바일 SwipeableView content 분기에 WatchlistTab 추가
    • 데스크탑 조건부 렌더 {activeTab === TAB_WATCHLIST && <WatchlistTab />} 추가
  • 탭 뱃지 = 관심종목 개수(훅에서 노출).

4. 컴포넌트 구조 (접근안 A: 훅 + 자립형 탭)

기존 HoldingsIntelTab 패턴(자립형 탭 컴포넌트 + api 헬퍼)에 상태 로직을 훅으로 분리한 형태.

src/pages/stock/
├── hooks/
│   └── useWatchlist.js         # CRUD + 알림 이력 상태·액션
├── components/
│   └── WatchlistTab.jsx        # 표현 (내부 소형 컴포넌트: WatchlistForm/Row, AlertCard)
├── watchlistUtils.js           # 순수 헬퍼 (라벨/색/시간 매핑)
└── watchlistUtils.test.js      # 헬퍼 유닛 테스트

4.1 useWatchlist.js (훅)

상태:

  • items: [] — 관심종목 목록
  • alerts: [] — 알림 이력
  • alertDays: 7 — 알림 기간 필터(1/7/30)
  • loading, error, adding (폼 제출 중)

액션:

  • load()getWatchlist() + getTradeAlerts(alertDays) 병렬 로드
  • add({ ticker, name, note }) — 낙관적 추가 → 성공 시 load() 재조회, 실패 시 롤백 + 에러
  • remove(ticker) — 낙관적 제거 → 실패 시 롤백
  • setAlertDays(days) — 변경 시 알림만 재조회

노출: { items, alerts, alertDays, setAlertDays, loading, error, adding, add, remove, load }

4.2 WatchlistTab.jsx (표현)

  • 마운트 시 load().
  • 상단 패널 — 관심종목 관리: 인라인 추가 폼(ticker 필수, name·note 선택) + 목록. 각 행: 종목명/코드/메모/등록일 + 삭제 버튼. 빈 상태 안내.
  • 하단 패널 — 최근 시그널: 기간 토글(1D/7D/30D) + 알림 카드. 카드: kind 뱃지, condition 한글 라벨, ticker/name, price, detail, fired_at 상대시간.
  • 로딩/에러/빈 상태: stock-panel · stock-error · stock-empty 등 기존 클래스 재사용.
  • 하단 면책 문구(hi-disclaimer 유사): "※ 어드바이저리 알림이며 자동매매가 아닙니다."

4.3 watchlistUtils.js (순수 헬퍼 — 테스트 대상)

KIND_META = { buy: { label: '매수', color, bg }, sell: { label: '매도', color, bg } }
CONDITION_LABEL = { buy_ma20_pullback: 'MA20 눌림 반등', buy_breakout: '박스 상단 돌파',
  buy_rsi_bounce: 'RSI 과매도 반등', sell_stop_loss: '손절 라인', sell_ma_break: '이평선 이탈',
  sell_take_profit: '목표가 도달', sell_climax: '과열 소진', sell_trailing_stop: '트레일링 스톱' }

kindMeta(kind)         // 미정의 → 회색 폴백 + 원문 label
conditionLabel(cond)   // 미정의 → 원문 그대로 반환
normalizeTicker(str)   // trim만 수행(한국 종목코드=6자리 숫자, 대문자화 불필요)
relativeTime(iso)      // '3분 전' / '2시간 전' / '어제' 등, 잘못된 값 → '' 폴백

5. API 레이어 (src/api.js 추가)

// ── Stock Watchlist / Trade Alerts ──
export const getWatchlist   = ()        => apiGet('/api/stock/watchlist');
export const addWatchlist   = (body)    => apiPost('/api/stock/watchlist', body);   // { ticker, name?, note? }
export const removeWatchlist= (ticker)  => apiDelete(`/api/stock/watchlist/${encodeURIComponent(ticker)}`);
export const getTradeAlerts = (days = 7)=> apiGet(`/api/stock/trade-alerts?days=${days}`);

전부 상대경로, 기존 apiGet/apiPost/apiDelete 재사용. getWatchlist/getTradeAlerts 응답은 훅에서 data.watchlist ?? data ?? [], data.alerts ?? data ?? [] 로 방어적 파싱.


6. UX / 상호작용 세부

  • 추가 폼: ticker 미입력 시 제출 비활성. 제출 중 adding → 버튼 로딩. 성공 시 폼 초기화.
  • 낙관적 갱신: add/remove 즉시 UI 반영, 실패 시 이전 상태 롤백 + stock-error 메시지.
  • 중복 방지: 이미 목록에 있는 ticker면 폼에서 안내(추가 차단).
  • 알림 카드 정렬: fired_at 내림차순(최신 우선).
  • 빈 상태: 관심종목 0개 / 알림 0개 각각 안내 문구.
  • 반응형: 데스크탑 2열/모바일 1열은 기존 stock-panel 그리드 관례 따름.

7. 스타일

src/pages/stock/Stock.css 하단에 wl-* 프리픽스 섹션 추가 (기존 hi-* 패턴과 동일 구성):

  • .wl-form, .wl-list, .wl-row, .wl-row__meta, .wl-del
  • .wl-alerts, .wl-alert, .wl-kind-badge, .wl-cond, .wl-period-toggle
  • 색상: 매수 초록 #22c55e, 매도 빨강 #ef4444 (기존 ACTION_MAP 팔레트와 일치).

8. 테스트 (TDD)

watchlistUtils.test.js — 순수 헬퍼 검증:

  1. conditionLabel: 정의된 8종 매핑 정확, 미정의 값은 원문 폴백.
  2. kindMeta: buy/sell 라벨·색, 미정의 kind 회색 폴백.
  3. relativeTime: 방금/분/시간/일 경계, 잘못된 입력 '' 폴백.
  4. normalizeTicker: 공백 trim.

컴포넌트/훅은 수동 검증(개발 서버 3007 + BE 계약) + 빌드/lint 통과로 확인. (기존 스크리너 훅 테스트처럼 필요 시 훅 테스트 추가 가능하나 v1 필수 아님.)


9. 완료 기준 (Acceptance)

  • 거래 데스크에 "관심종목" 탭 노출(데스크탑·모바일), 뱃지에 개수 표시.
  • 종목 추가/삭제가 BE 계약대로 동작(낙관적 갱신 + 실패 롤백).
  • 최근 알림 이력이 기간 토글별로 조회되고, kind/condition 한글 라벨·색으로 표시.
  • watchlistUtils.test.js 통과.
  • npm run lint · npm run build 통과.

10. 리스크 / 오픈 이슈

  • 응답 래핑 형태 미확정: BE가 { watchlist: [...] } 인지 배열 직접인지 문구 기준 불확실 → 방어적 파싱으로 흡수.
  • 알림 엔드포인트 미배포 가능성: BE 세션 미완 시 GET /api/stock/trade-alerts 404/네트워크 오류 → 알림 패널은 에러 상태를 조용히 표시하고 관심종목 CRUD는 독립 동작하도록 분리.
  • params 편집: v1 제외. 추후 BE POST/PUT params 계약 확정 후 별도 스펙으로 확장.