SEAN TEAM
Work · WEB + NATIVE PORTFOLIO --:-- 서울 · 로컬에서 도는 빌드 · 웹 + 앱 + API

계좌가 여러 개여도
포트폴리오는
하나입니다.

Asset Pocket

시세를 못 받았다고자산이 0원이 되지는 않습니다.모르면 모른다고 적습니다.

ABOUT · WHAT IT DOES

Asset Pocket 은 여러 증권 계좌의 주식과 예수금을 한 화면에 모으는 포트폴리오 앱입니다. 웹과 iOS·안드로이드 앱이 같은 API, 같은 계정을 씁니다. 계좌마다 보유를 직접 적거나 거래 원장으로 쌓을 수 있고, CSV 는 저장하기 전에 미리보기로 검사합니다. 시세·환율·뉴스를 못 받으면 빈칸을 추측으로 채우지 않고, 무엇을 몰라서 합계에서 뺐는지 화면에 적습니다.

지금은 로컬에서 도는 빌드입니다. 시세(KIS)와 뉴스(네이버) 키를 넣지 않은 상태라, 아래 화면은 “공급자 미설정”일 때 제품이 어떻게 말하는지를 그대로 보여 줍니다.

01 — Holdings screen

합계를 낼 수 없을 때도 화면은 말을 합니다.

Asset Pocket 실제 자산 화면 — 시세 공급자가 설정되지 않아 총 평가금액 자리에 ‘평가할 수 없음’이 뜨고, 매입 ₩12,019,627 · 예수금 ₩2,622,873, 구성 비중은 매입금액 기준으로 보인다
  1. 공급자 미설정키가 없어 가져오지 않는 것이지 장애가 아니라고 맨 위에 적습니다.
  2. 평가할 수 없음보유 5종목의 시세를 하나도 받지 못했습니다. 0원이라는 뜻이 아닙니다.
  3. 매입금액 기준 구성평가금액으로 비중을 낼 수 없으면 가진 근거로 나누고, 그렇다고 표시합니다.
02 — From account to allocation

계좌를 넣고, 값을 매기고, 나눠 봅니다.

세 화면은 같은 데모 계정입니다. 국내 계좌 하나와 해외(USD) 계좌 하나, 보유 종목 5개.

  1. ① Accounts

    보유를 적거나, 거래를 쌓거나

    계좌마다 두 방식 중 하나를 고릅니다. 보유 수량을 직접 적는 방식과, 거래 원장을 쌓으면 보유가 계산되는 방식. 원장 계좌의 보유는 고쳐 쓰지 않고 매번 원장 전체를 재생해서 다시 만듭니다.

  2. ② Valuation

    받은 시세만큼만 더합니다

    합계는 시세를 받은 종목으로만 냅니다. 하나도 받지 못하면 숫자 대신 “평가할 수 없음”을 띄우고, 매입금액과 예수금은 따로 보여 줍니다. 합계에서 뺀 종목은 코드까지 적습니다.

  3. ③ Allocation

    근거가 바뀌면 이름표도 바뀝니다

    구성 비중은 원래 평가금액으로 나눕니다. 시세가 비어 있으면 매입금액 기준으로 대신 나누고, 제목 옆에 그 사실을 붙입니다. 계좌별 · 자산 유형별 · 통화별 · 국가별 네 갈래입니다.

계좌 화면 — 국내 주식계좌(예수금 ₩1,500,000, KRW)와 해외 주식계좌(예수금 $820.50, USD), 둘 다 보유 직접 입력
총 평가금액 카드 — ‘평가할 수 없음’, 실시간 끊김 · 30초 주기 갱신 표시, 아래에 시세를 받지 못한 5개 종목 목록
구성 영역 — ‘매입금액 기준’ 표시와 계좌별(국내 62.6% · 해외 37.4%), 자산 유형별(주식 75.9% · ETF 24.1%) 막대

모르는 것을 말하는 네 가지 방법.

README 첫머리의 규칙은 “가짜 데이터를 만들지 않는다”입니다. 랜덤값도, 직전 값 재사용도, 0 이나 매입가로 메우기도 없습니다.

네 가지 상태 총 평가금액 카드 실제 화면 — ‘평가할 수 없음’과 ‘자산이 0원이라는 뜻이 아닙니다’ 문구, 매입 ₩12,019,627 · 예수금 ₩2,622,873 평가할 수 없음합계를 낼 근거가 없다는 뜻입니다. 0원과 같은 모양이면 버그로 봅니다. 시세 없음 · 환산 불가 · 공급자 미설정과 섞지 않습니다.
KIS네이버 뉴스 유리 질감의 상단 메뉴와 배너 실제 화면 — ‘시세 · 뉴스 공급자가 설정되지 않았습니다’, ‘장애가 아니라 미설정입니다’, quote:kis, news:naver 공급자 미설정키가 없어 아예 가져오지 않는 상태입니다. 장애가 아니므로 기다리라고 하지 않고, 키를 넣으라고 합니다.
시세 없음 경고 실제 화면 — ‘5개 종목의 시세를 가져오지 못했습니다’, XKRX:005930 · XKRX:069500 · XKRX:000660 · XNAS:AAPL · XNAS:NVDA, 공급자 kis, 아래에 ‘데이터를 못 가져온 것이 아닙니다’ 자산 추이 안내 뺀 종목은 이름까지시세를 못 받은 종목은 합계에서 빼고, 무엇을 뺐는지 종목 코드로 적습니다. 어느 공급자에서 비었는지도 함께 씁니다.
구성 비중 구성 실제 화면 — ‘매입금액 기준’ 표시와 계좌별 비중(국내 62.6% · 해외 37.4%) 비중의 기준평가금액으로 나눌 수 없을 때는 매입금액으로 나누고, 그렇다고 적습니다. 차트 색은 실제 배경색 위에서 색각 검사를 통과시킨 값입니다.

시세마다 한국투자증권 KIS RESTKIS WebSocket네이버 뉴스 검색open.er-api 환율exchangerate.hostKRX 상장법인 목록 출처와 시각이 붙습니다.

공급자는 인터페이스 뒤에 있어, registry.ts 한 곳에 등록하면 나머지 코드를 건드리지 않고 바뀝니다. 지금 무엇이 오고 있는지는 GET /providers/status 와 화면 맨 위 배너가 알려 줍니다.

01

가짜 데이터를 만들지 않습니다

시세·뉴스·환율을 못 받으면 그 사실을 그대로 보여 줍니다. 틀린 환율로 계산한 총액은 비어 있는 것보다 나쁩니다. 그래서 환율이 없으면 그 계좌를 “환산 불가”로 두고 합계에서 뺍니다.

02

남의 데이터는 DB 가 막습니다

사용자 소유 테이블 9개에 Postgres 행 단위 보안이 걸려 있습니다. 앱은 테이블 소유자가 아닌 별도 롤로 접속하고, 사용자 맥락이 없으면 0건이 나옵니다. 코드가 where userId 를 빠뜨려도 새지 않습니다.

03

돈은 number 가 아닙니다

DB 안에서는 Decimal, JSON 경계에서는 문자열입니다. 평단에는 매수 수수료가 들어가고, 실현손익도 같은 이동평균으로 계산해 평가손익과 합이 맞습니다.

04

원장이 정본입니다

원장 계좌의 보유 종목은 저장된 값이 아니라 원장을 재생한 결과입니다. 증분 갱신은 거래를 지우거나 순서를 바꿀 때 어긋나기 때문입니다. 매도가 기대고 있는 매수를 지우려 하면 거절합니다.

05

웹과 앱이 같은 문을 씁니다

같은 엔드포인트, 같은 계정입니다. 다른 건 refresh 토큰을 두는 곳뿐입니다. 웹은 httpOnly 쿠키, 앱은 키체인. 이미 회전된 토큰이 다시 오면 유출로 보고 그 세션 계보를 전부 끊습니다.

06

CSV 는 먼저 보여 줍니다

보유와 거래를 CSV 로 올리면 저장하기 전에 미리보기(dryRun)로 검사합니다. 실패한 줄은 파일의 실제 줄 번호와 사유로 돌려줍니다.

03 — Counted from the repo · 2026-09-25
35,624줄의 TypeScript · 테스트 제외
346개의 테스트 케이스 · 파일 33개
34개의 화면 · 웹 13 + 앱 21
9개의 테이블에 행 단위 보안
2,595개 KRX 상장법인 종목 마스터
ROLE
제품 설계 · 풀스택 개발
WEB
Next.js 15 · React 19 · TanStack Query · SSE 실시간 시세
APP
Expo 57 · React Native 0.86 · expo-blur · 30초 폴링
API
Fastify 5 · 별도 수집 워커
DATA
PostgreSQL 16 (RLS) · Prisma 6 · Redis
TESTS
Vitest · 8개 프로젝트 타입 검사 · RLS 검증 스크립트
STATUS
FUNCTIONAL LOCAL BUILD · WEB + MOBILE + API
WHY

만들면서 스스로에게
물은 것들.

왜 0원 대신 “평가할 수 없음”인가요?

시세를 하나도 받지 못한 포트폴리오의 합계를 0원으로 적으면, 보는 사람은 자산이 사라졌다고 읽습니다. 그래서 화면이 구분하는 상태를 넷으로 나눴습니다. 시세 없음, 환산 불가, 평가할 수 없음, 공급자 미설정. 넷은 뜻도, 사용자가 할 일도 다릅니다. 가장 여러 번 다시 고친 부분이라, 어떤 리팩터링에서도 합치지 말라고 인계 문서에 적어 두었습니다.

환율을 못 받으면 어떻게 되나요?

통화가 다른 계좌는 “환산 불가”로 표시하고 전체 합계에서 뺍니다. 추측한 환율로 메우지 않습니다. 기본 환율 공급자인 open.er-api 는 키 없이 166개 통화를 하루 한 번 받아 옵니다. 환율은 워커가 채우므로, 워커가 꺼져 있으면 들어오지 않는다는 것도 문서에 적어 두었습니다.

KIS 실시간 구독이 41종목으로 제한되면요?

보유와 관심 종목이 41개를 넘는 건 흔합니다. 넘친 종목을 버리지 않고 REST 폴링으로 내립니다. 시세는 계속 나오고 갱신 주기만 달라집니다. 화면에 보이는 종목이 슬롯을 먼저 가져가고, 무엇이 실시간이고 무엇이 폴링인지 화면이 말할 수 있어야 한다는 것이 구독 슬롯 매니저의 규칙입니다.

같은 사건의 뉴스는 어떻게 묶나요?

처음에는 simhash 로 제목을 묶으려 했습니다. 한국어 뉴스 제목 표본으로 재 보니 같은 사건과 다른 사건의 해밍거리가 겹쳐 선을 그을 수 없었습니다. 같은 표본에서 문자 2-gram 포함도는 깨끗하게 갈렸습니다. 같은 사건 0.46 이상, 다른 사건 0.07 이하. 그래서 simhash 는 저장과 완전 중복 판정에, 제목 유사도는 묶기에 씁니다.

아직 안 되는 건 무엇인가요?

KIS 시세 파서는 실제 키로 검증하지 않았습니다. 공식 문서 기준으로 짰고, 필드가 없으면 값을 지어내지 않고 실패로 떨어집니다. 앱은 실시간 스트림 대신 30초 폴링이고, 비밀번호 재설정 메일은 발송 어댑터가 없어 나가지 않습니다 — 화면이 그렇게 말합니다. EAS 빌드는 아직 한 번도 돌리지 않았습니다.

모르는 건모른다고 적는 포트폴리오.

프로젝트 전체 보기 →