포트폴리오 리밸런싱 계산기를 직접 만든 개발기를, 데이터·계산 등 실제로 마주친 문제와 선택을 중심으로 정리했습니다.

쓸 만한 게 없어서 직접 만들었습니다
주식·ETF를 목표 비중대로 맞추려면 "무엇을 몇 주 사고팔지"를 계산해야 하는데, 막상 해보면 손이 많이 갑니다. 현재가에 환율까지 얽히고, 정수 단위로 딱 떨어지지도 않죠. 스프레드시트로 매번하기가 번거로워서, 결국 도구를 하나 직접 만들기로 했습니다. 이 글은 그 과정에서 마주친 문제와 선택을 개발자 관점에서 정리한 기록입니다.
기술 스택은 단순하게 잡았습니다. Next.js(App Router)로 프론트를 짜고, 별도 백엔드 DB 없이 사용자의 입력은 브라우저 localStorage에만 저장하도록 했죠. 시세 같은 외부 데이터만 서버리스라우트로 중계했습니다. 로그인도 서버도 최소화하니 유지비가 거의 안 들고 배포도 가벼웠습니다.
진짜 문제는 UI가 아니라 '데이터'였습니다
계산 로직이나 화면은 예상 범위였는데, 발목을 잡은 건 시세 데이터였습니다. 처음엔 익숙한 해외 금융 API를 붙였는데, 배포 서버에서 호출하니 곧바로 429(요청 과다)로 막히더군요. 데이터센터 IP가 차단 대상이었던 겁니다. 로컬에선 잘 되던 게 배포에선 안 되니 꽤 당황했습니다.
몇 가지를 돌려보다가, 국내 포털의 비공식 엔드포인트로 방향을 틀었습니다. 마침 한국 주식·미국 주식·환율·종목 검색이 한 소스에서 다 해결되더군요. 다만 브라우저에서 직접 부르면 CORS에 막히고 남용 위험도 있어서, 서버리스 라우트로 감싸 프록시로 두었습니다. 여기에 짧은 캐시와 IP별 레이트리밋을 붙였죠. 정리하면 이런 구조가 됐습니다.
- 클라이언트는 우리
/api/*만 호출 → 외부 소스는 서버에서만 접근 - 응답은 짧게 캐시해 같은 시세를 반복 조회하지 않음
- IP별 요청 제한으로 남용·과금 폭주 방지
첫째는 정수 주수입니다. 국내 주식은 소수점 매매가 안 되니, 목표에 딱 맞추려면 대개 소수 주가 나오는데 이걸 반올림해야 합니다. 그러면 목표와 미세하게 어긋나고, 그 차이만큼 현금이 남거나 모자라죠. 그래서 결과에 '예상 잔여 현금'을 함께 보여 주기로 했습니다. 딱 떨어지는 척하지 않는 편이 정직하니까요. 계산은 단순한데, 함정이 몇 개 있었습니다. 검색도 같은 프록시를 탑니다. 사용자가 종목 이름이나 코드를 입력하면 디바운스를 걸어 서버로 넘기고, 서버가 국내·해외 종목과 ETF를 한 번에 찾아 돌려주죠. 이렇게 해 두니 프론트는 우리 API 하나만 알면 되고, 데이터 소스가 바뀌어도 클라이언트 코드는 손댈 필요가 없었습니다. 소스를 갈아 끼운 경험을 하고 나니, 이 경계를 처음부터 그어 둔 게 다행이었죠.
응답은 우리 포맷으로 정규화(가격·통화·거래소만 추림)외부 데이터를 쓰는 도구는 결국 "데이터가 끊길 때 어떻게 버티나"가 절반이라는 걸 이때 체감했습니다. 화면은 하루면 만들어도, 데이터 파이프라인은 며칠을 잡아먹었죠.핵심 계산은 어렵지 않습니다. 전체 평가액을 구하고, 종목별 목표 비중으로 목표 금액을 낸 뒤, 현재 금액과의 차이를 현재가로 나눠 몇 주를 사고팔지 정하면 되죠. 문제는 그 사이에 낀 두 가지함정이었습니다.둘째는 환율입니다. 미국 주식은 달러, 국내 주식은 원화라 그냥 더할 수가 없습니다. 그래서 기준 통화를 하나 정하고, 모든 금액을 그 통화로 환산해 합산하도록 했습니다. 환산 함수는 이런 모양이었죠.
function fxConvert(amount, from, base, usdKrw) {
if (from === base) return amount;
if (from === "USD" && base === "KRW") return amount * usdKrw;
if (from === "KRW" && base === "USD") return amount / usdKrw;
return amount;
}
별것 아닌 함수지만, 이걸 빼먹으면 원화와 달러가 뒤섞여 비중이 엉뚱하게 나옵니다. 기준 통화를 토글로 바꿀 수 있게 하니, 같은 계좌도 원화로 볼 때와 달러로 볼 때가 다르게 보이는 걸 눈으로 확인할 수 있었습니다.
작은 선택들이 도구의 성격을 만들었습니다색 관례부터 봤습니다. 매수는 빨강, 매도는 파랑으로 칠했습니다. 서구권 관례(상승 초록·하락 빨강)와 반대라, 라이브러리 기본색을 그대로 쓰면 한국 사용자에겐 오히려 헷갈리죠. 사소해 보여도 "이도구는 국내 사용자를 기준으로 만들었다"는 신호가 됩니다.마지막은 저장 방식입니다. 포트폴리오는 서버에 보내지 않고 브라우저에만 저장합니다. 남의 자산 정보를 우리가 들고 있을 이유가 없고, 백엔드가 없으니 관리 포인트도 줄죠. 대신 기기를 바꾸면 사라진다는 한계는 그대로 고지했습니다.
돌아보면 어려운 알고리즘이 있는 프로젝트는 아니었습니다. 그보다 데이터가 끊길 때 버티는 법, 정수·환율 같은 현실의 잔가지를 어떻게 정직하게 드러낼지, 그리고 "조언하지 않는다"는 경계를어디에 그을지가 진짜 고민이었죠. 작은 도구 하나에도 결정할 게 이렇게 많다는 걸 다시 배웠습니다.
직접 사용해보기
이렇게 만든 도구는 지금 무료로 공개해 두었습니다. 종목을 검색해 담고, 보유 수량과 목표 비중을 넣으면 현재가 기준으로 몇 주를 사고팔면 되는지 계산해 줍니다.
직접 써 보고 싶으시면 리밸런싱 고(RebalanceGo)에서 확인해 보세요.
특정 종목을 추천하거나 수익을 약속하지 않고, 입력값 기준의 계산과 개념 설명만 제공하죠. 검색·광고 정책에서 민감하게 보는 영역이기도 해서, 문구 하나까지 조심했습니다.
기능이 비슷해도 디테일에서 도구의 성격이 갈립니다. 신경 쓴 선택 몇 가지를 적어 둡니다.
두 함정을 합치면 계산은 이렇게 흘러갑니다. 먼저 모든 자산을 기준 통화로 환산해 전체 평가액을 구하고, 목표 비중으로 각자의 목표 금액을 냅니다. 목표에서 현재를 뺀 차이를 그 종목의 환산 현재가로 나눠 반올림하면, 사고팔 정수 주수가 나오죠. 부호가 양수면 매수, 음수면 매도로 갈리고요. 로직 자체는 짧지만, 환산과 반올림을 어디에서 처리하느냐에 따라 결과가 미묘하게 달라져서 순서를 몇 번 고쳐 잡았습니다.