📊 이슈 ETF 시스템 문서
정치 이슈별 구성종목 묶음 상품 — 시스템이 AP(지정참가회사) 역할 수행
1. 라이프사이클
발제→펀딩→선발→상장(Active)→리밸런싱→해산
| 단계 | 설명 | 주요 행위자 |
|---|
| 펀딩 | 시드 자금 모집, 확신 보증금 10,000 Voice | 발제자 + 시드펀더 |
| 선발 | 정치인 신청 + 시드펀더 O/X 투표 (50% 이상 찬성) | 정치인 + 시드펀더 |
| 상장 | 시드머니로 구성종목 매입, ETF 주식 발행 | 시스템(AP) |
| 거래 | NAV 기반 매수(생성)/매도(환매), ±0.5% 스프레드 | 일반 사용자 |
| 리밸런싱 | 90일 주기, 보유자 투표로 구성종목 교체 | ETF 보유자 |
| 해산 | 투표 또는 구성종목 부족 시 자동 | 시스템 |
2. 사용자 API
| 메서드 | 경로 | 설명 |
|---|
| GET | /api/etf/get-etfs | ETF 목록 조회 |
| POST | /api/etf/create-proposal | ETF 발제 (확신 보증금 차감) |
| POST | /api/etf/contribute-funding | 펀딩 참여 (Voice 차감) |
| POST | /api/etf/politician-apply | 정치인 신청 |
| POST | /api/etf/vote-selection | 선발 투표 (시드펀더만) |
| POST | /api/etf/buy-etf | ETF 매수 (NAV + 0.5%) |
| POST | /api/etf/sell-etf | ETF 매도 (NAV - 0.5%, 보유량 검증) |
| POST | /api/etf/rebalance | 리밸런싱 (start/vote/finalize) |
| POST | /api/etf/dissolve | ETF 해산 |
3. 어드민 API
| 메서드 | 경로 | 설명 |
|---|
| GET | /api/admin/etf/list | ETF 목록 (상태 필터 + 통계) |
| GET | /api/admin/etf/detail?id= | 상세 (정치인 이름 resolved, 보유자 수) |
| POST | /api/admin/etf/actions | 관리자 액션 |
관리자 액션 목록
| Action | 설명 | 허용 상태 |
|---|
| force-dissolve | 강제 해산 (펀딩→환불, 활성→보유자 배분) | 전체 |
| finalize-selection | 선발 수동 마감 | selection |
| expire-funding | 펀딩 만료 + 환불 | funding |
| update-nav | NAV 강제 재계산 | active, rebalancing |
| start-rebalancing | 리밸런싱 시작 | active |
4. 자동화 Cron
| Job Type | 설명 | 주기 |
|---|
| etf_funding_check | 펀딩 만료 ETF 자동 환불 | 1일 1회 |
| etf_rebalancing_trigger | nextRebalancingDate 초과 → 리밸런싱 자동 시작 | 1일 1회 |
POST /api/admin/run-scheduled-job
{ "jobType": "etf_funding_check", "targetDate": "2026-02-21" }5. 금융 플로우
NAV 계산
NAV = Σ(구성종목별_보유주식수 × 현재가) ÷ 총발행주식수
매수 (생성)
1. 사용자 voiceAmount 지불
2. 매수가격 = NAV × 1.005 (0.5% 스프레드)
3. 생성 주식수 = floor(voiceAmount / 매수가격)
4. 시스템(AP): 구성종목 비율대로 stocks 매입
5. ETF 주식 발행 → 포트폴리오 추가
6. Voice 차감 (Firestore 트랜잭션)
매도 (환매)
1. 보유량 검증 (portfolios 컬렉션)
2. 매도가격 = NAV × 0.995 (0.5% 스프레드)
3. 시스템(AP): 구성종목 비율대로 stocks 매도
4. ETF 주식 소각
5. Voice 지급 (Firestore 트랜잭션)
6. 핵심 설정값
| 설정 | 값 | 설명 |
|---|
| CONVICTION_DEPOSIT | 10,000 Voice | 확신 보증금 |
| MIN_FUNDING_GOAL | 10,000,000 Voice | 최소 모집 금액 |
| MIN/MAX_CONSTITUENTS | 3~10 | 구성종목 수 범위 |
| MIN_APPROVAL_RATE | 50% | 선발 최소 찬성률 |
| CREATION_SPREAD | 0.5% | 생성(매수) 스프레드 |
| REDEMPTION_SPREAD | 0.5% | 환매(매도) 스프레드 |
| REBALANCING_PERIOD | 90일 | 리밸런싱 주기 |
| REMOVAL_THRESHOLD | 2/3 | 퇴출 정족수 |
| INITIAL_PRICE | 1,000 Voice | 기준가 |
7. Firestore 스키마
| 컬렉션 | 용도 |
|---|
| etfs | ETF 문서 (라이프사이클, 구성종목, 펀딩 등) |
| etfs/{id}/rebalancing_votes | 리밸런싱 투표 기록 (서브컬렉션) |
| users | voiceBalance 차감/지급 |
| portfolios | ETF 보유량 (holdings[].assetType === "etf") |
| stocks | 구성종목 현재가 (currentPrice) |
| politicians | 정치인 이름 resolving (displayName) |
| scheduled_jobs | ETF Cron 작업 기록 |
8. 파일 구조
src/
├── services/etfService.ts # 핵심 비즈니스 로직 (Firestore)
├── pages/api/etf/
│ ├── _auth.ts # 인증 헬퍼
│ ├── get-etfs.ts / create-proposal.ts / contribute-funding.ts
│ ├── politician-apply.ts / vote-selection.ts
│ ├── buy-etf.ts / sell-etf.ts / rebalance.ts / dissolve.ts
├── pages/api/admin/etf/
│ ├── list.ts / detail.ts / actions.ts
├── app/admin/etf/page.tsx # 어드민 관리 UI
├── components/etf/
│ ├── ETFList.tsx / ETFCard.tsx / ETFScreen.tsx
│ ├── ETFDetailScreen.tsx / ETFProposalForm.tsx
└── types/index.ts # ETF 타입 정의