📝 한줄 요약
미국 주식·ETF 한 계좌의 거래를 정확하게 기록하는 로컬 대시보드를 만들었습니다. 테스트 1,109개를 통과한 상태에서 실제 주식 API를 처음 호출했는데, 거기서 두 번 틀렸습니다. 이 글은 그 두 번을 어떻게 찾아냈는지에 대한 기록입니다.
바쁘시면 이것만 읽어도 돼요:
차 트부터 만들고 싶었지만, 제일 어려운 건 차트가 아니라 매수·매도가 반복돼도 평균단가가 한 푼도 틀리지 않는 거래원장이었습니다.
AI에게 코드를 맡기되 요구사항·검증 기준·최종 판단은 사람이 쥐었습니다. AI끼리도 구현·검수 역할을 나눴습니다.
모의 응답으로 만든 테스트가 전부 통과했는데, 실제 API 응답에는 테스트에 있던 필드 하나가 아예 없었습니다.
실제 데이터를 저장하고 나서야 "오늘 날짜 데이터는 아직 확정된 종가가 아니다" 는 걸 거래량을 보고 알아챘습니다.
배운 것: '테스트 통과'와 '실제로 쓸 수 있다'는 같은 말이 아니었습니다.
🎯 이런 분들께 도움돼요
AI로 만든 결과물을 어디까지 믿어도 되는지 기준이 필요한 분
돈·숫자를 다루는 도구를 만들면서 "이거 틀리면 안 되는데" 가 걱정인 분
무료 API를 붙여보려는데 호출 한도와 요금이 무서운 분
AI 에이전트를 여러 대 쓰면서 역할을 어떻게 나눌지 고민 중인 분
😫 문제 상황 (Before)
처음에는 차트가 있는 주식 대시보드를 떠올렸습니다. 하지만 막상 만들기 시작하니 가장 어려운 문제는 차트가 아니라, 매수와 매도가 반복돼도 평균단가와 손익이 한 푼도 틀리지 않게 계산되는 거래원장이었습니다.
증권사 화면만으로는 제가 원하는 기준의 거래원장·이동평균단가·수수료 반영 실현손익을 한곳에서 보기 어려 웠습니다. 관심종목, 환율, 위험 관리까지 욕심이 났지만, 그 전에 가장 먼저 틀리면 안 되는 것부터 만들기로 하고 범위를 줄였습니다. "한 계좌의 미국 주식·ETF 거래를 정확하게 기록하는 로컬 대시보드."
🛠️ 사용한 도구
도구: Claude Code (구현), AI 에이전트 빅맥 (PM·QA), 독립 리뷰어 (읽기 전용 검수)
기술: Streamlit 화면, DuckDB 저장, Python
데이터: Financial Modeling Prep 무료 플랜 (하루 250회 한도)
기록: Obsidian에 설계·프롬프트·결과 전부 남김
🔧 작업 과정
역할을 먼저 나눴습니다 — "AI가 알아서"가 아니라
이번에 제일 잘한 선택은 AI 사이에도 권한을 나눈 것입니다.
저스틴(저): 목표, 범위, 중요한 제품 결정, 실제 서비스 가입과 외부 호출 승인
빅맥: PM·QA. 완료 조건을 정하고, 숫자를 검증하고, 통합 보고
Claude Code: 테스트를 먼저 쓰고 그 다음 코드를 쓰는 방식으로 구현
독립 리뷰어: 구현한 쪽과 분리해서, 반대 관점으로만 검수
특히 실제 외부 서비스 호출은 제 승인 없이는 못 하게 막아뒀습니다. 나중에 이게 크게 도움이 됩니다.
차트보다 먼저 — 10주 사고 2주 팔면 남은 8주의 원가는?
거래원장부터 만들면서 계속 물었던 질문은 이런 것들이었습니다.
10주를 사고 2주를 팔면 남은 8주의 원가는 어떻게 되나?
수수료는 평균단가에 넣나, 실현손익에서 빼나?
저장이 됐는지 안 됐는지 모를 때 자동으로 다시 시도해도 되나?
마지막 질문이 제일 중요했습니다. 저장 결과가 불확실할 때 프로그램이 알아서 재시도하면, 이미 저장된 거래가 한 번 더 들어갈 수 있습니다. 그래서 불확실하면 멈추고 사람에게 알리는 쪽으로 정했습니다. 보유 수량보다 많이 파는 입력도 저장 전에 막았습니다.
여기까지 오는 동안 화면은 아직 없었습니다. 숫자가 먼저였습니다.
무료 한도 250회를 어떻게 안 넘길 것인가
실제 주식 데이터를 붙일 차례가 되자 새로운 걱정이 생겼습니다. 무료 플랜은 하루 250회입니다. 실수로 반 복 실행하면 그날은 끝입니다.
그래서 이렇게 정했습니다.
호출하기 전에 오늘 몇 번 썼는지 먼저 기록하고, 그 다음에 호출
실패한 호출도 한 번 쓴 것으로 셈 (실패는 한도를 소모하니까)
기본 실행은 아무것도 건드리지 않는 미리보기(dry-run)
진짜 호출은 명령에
--execute를 직접 붙여야만
미리보기를 돌리면 이렇게만 나옵니다. API 키는 "설정됨"이라고만 표시되고 값은 절대 찍히지 않습니다.
모드: dry-run (실제 호출·저장 없음)
종목(2): AAPL, SPY
기간: 2021-08-04 ~ 2026-08-03
예상 최대 호출 수: 2 (하루 상한 250)
API 키: configured
사건 1 — 테스트에는 있는데 진짜 응답에는 없는 필드
준비를 다 하고, 제 승인 아래 딱 2번만 실제로 호출했습니다. AAPL 하나, SPY 하나.
그런데 둘 다 실패했습니다.
응답은 정상이었습니다. HTTP 200에 22행이 왔습니다. 문제는 테스트에서 쓰던 가짜 응답에는 있던 adjClose(배당까지 반영한 종가) 필드가 실제 응답에는 아예 없었다는 것이었습니다. 프로그램은 그 필드를 필수로 요구하고 있었으니 전부 거부한 겁니다.
진단할 때도 규칙을 지켰습니다. 응답 원문과 API 키는 출력하지 않고, 필드 이름만 확인했습니다.
수정은 이렇게 했습니다. 그 필드가 있으면 원래대로 쓰고, 없으면 이미 검증된 종가를 대신 씁니다. 다만 값이 있는데 이상하면 조용히 대체하지 않고 거부합니다. 대체해버리면 API가 바뀐 사실 자체가 가려지니까요. 그리고 이 값은 배당까지 반영한 값이 아니라는 걸 코드에 분명히 적어뒀습니다.
테스트가 틀린 게 아니었습니다. 테스트가 현실의 응답을 충분히 닮지 못했던 것입니다. 이 경험으로 '테스트 통과'와 '실제 사용 가능'은 같은 말이 아니라는 걸 배웠습니다.
고친 뒤 AAPL 1,254행, SPY 1,254행, 총 2,508행이 5년치로 저장됐습니다. 중복·빈 값·음수·앞뒤가 안 맞는 가격은 0건이었습니다.
사건 2 — 거래량이 이상한데요?
저장된 데이터를 훑어보다가 숫자 하나가 눈에 걸렸습니다.
종목
8월 4일 거래량
8월 3일 거래량
AAPL
12,962,987
75,051,951
SPY
8,199,359
58,881,217
전날의 6분의 1도 안 됩니다. 수집한 시각이 미국장이 열린 지 얼마 안 된 때였습니다. 즉 이 숫자는 그날의 최종값이 아니라 아직 진행 중인 값이었습니다. 공식 문서를 보니 가격·거래량 데이터는 5~15분마다 갱신된다고 돼 있었습니다.
여기서 재미있는 일이 생겼습니다. 저희는 "이미 저장한 데이터는 덮어쓰지 않는다" 는 안전 규칙을 두고 있었습니다. 중복 저장과 예기치 않은 수정을 막으려던 규칙인데, 장중 값을 먼저 저장해버리니 오히려 그 값이 영영 확정값으로 안 바 뀌게 된 겁니다.
안전 규칙끼리 부딪힌 셈입니다. 그래서 미국 동부시간 16시 30분(장 마감 + 반영 여유) 전에는 그날 데이터를 아예 받지 않도록 경계를 넣었습니다. 날짜를 직접 지정해도 조용히 잘라내지 않고 거부합니다.
실행할 수 없습니다: --to는 2026-08-03보다 뒤일 수 없다: 2026-08-04.
미국장 마감 후 그날 일봉이 확정된 뒤에 실행해야 한다
이 수정은 구현과 테스트(1,133개 통과)를 마쳤고, 지금은 검수를 기다리는 중입니다. 이미 저장돼버린 장중 데이터 2행도 백업 후 정리할 예정입니다. 아직 끝난 게 아닙니다.
✅ 결과 (After)
지금 되는 것
항목
상태
거래 추가·조회·수정
✅
삭제와 복구 (되돌릴 수 있는 삭제)
✅
보유 수량·평균단가·누적원가·실현손익
✅
초과매도·잘못된 입력 차단
✅
실제 주식 가격 5년치 수집
✅
호출 한도 관리와 미리보기 실행
✅
현재가·평가금액·미실현손익
⏳ 연결 전
환율·차트·관심종목·위험 관리
⏳ 다음 단계
현재 화면
⚠️ 아래 화면은 실제 투자내역이 아니라 임시 DB의 데모 데이터입니다.
데모 거래 3건(AAPL 10주 매수 → 2주 매도, SPY 3주 매수)을 넣으면 보유 종목 2개, 활성 거래 3건, 누적 실현손익 $39.30, AAPL 8주·SPY 3주가 계산돼 나옵니다.
현재가와 평가금액 칸은 준비 전이라고 표시됩니다. 0원으로 채우지 않았습니다. 된 것과 안 된 것을 화면에서 구분하는 게 낫다고 판단했습니다.
💬 이 과정에서 배운 AI 활용 팁
효과적이었던 것
AI에게 "구현"을, 사람에게 "검증 기준"을 맡기기. 코드를 많이 쓰게 하는 것보다, 무엇을 믿어도 되는지 판단하는 기준을 만드는 게 훨씬 중요했습니다.
구현하는 AI와 검수하는 AI를 분리하기. 같은 AI에게 "네가 만든 거 검토해봐"라고 하면 자기 가정을 그대로 통과시킵니다. 반대 관점만 보는 역할을 따로 뒀습니다.
외부 서비스 호출은 사람 승인을 거치게 만들기. 이번에 실제 호출은 딱 2번이었습니다. 무료 한도를 태우지 않았습니다.
실패를 숨기지 않게 시키기. "데이터가 없으면 0으로 채우지 말고 '없음'이라고 표시해"라고 처음부터 못 박았습니다.
이렇게 하면 안 돼요
가짜 응답으로 만든 테스트를 100% 믿기. 저희는 1,109개를 통과한 상태에서 실제 API에 처음 붙었다가 바로 틀렸습니다. 가능하면 일찍, 최소 범위로 실제 호출을 해보세요.
모든 희귀한 오류를 출시 전에 다 막으려 하기. 끝이 안 납니다. 정상 사용 흐름·비밀정보 노출·데이터 손상만 "지금 고칠 것"으로 삼고 나머지는 미뤘습니다.
"AI가 다 만들어줬다"고 생각하기. 이번에 발견한 두 문제는 둘 다 실제 데이터를 사람이 눈으로 보다가 찾았습니다. 거래량 숫자가 이상하다고 느낀 건 AI가 아니었습니다.
🌍 다른 업무에 적용한다면?
숫자가 틀리면 곤란한 모든 자동화에 그대로 적용됩니다. 정산, 재고, 근태, 매출 집계 같은 것들이요. 핵심은 세 가지입니다. ① 화면보다 계산을 먼저 맞춘다 ② 외부에서 받아온 데이터는 형식을 의심한다 ③ 확실하지 않으면 자동으로 재시도하지 말고 사람에게 알린다.
🚀 앞으로의 계획
미국장 확정 데이터 경계 검수 마무리, 잘못 저장된 2행 정리
현재가·평가금액·미실현손익을 화면에 연결
실제 데이터가 없어도 볼 수 있는 데모 모드
원/달러 환율과 원화 표시
가격 차트, 관심종목, 위험 관리
📋 재사용 가능한 프롬프트
프롬프트 1: 돈·숫자를 다루는 기능 만들 때
[만들 기능]을 구현해줘. 단, 다음을 지켜줘.
데이터가 없으면 0이나 빈 값으로 채우지 말고 "없음"으로 표시할 것
저장이 성공했는지 불확실하면 자동으로 재시도하지 말고 멈추고 알릴 것
계산이 틀릴 수 있는 경계(반올림, 나눗셈, 수수료 반영 위치)를 먼저 나에게 질문할 것
테스트를 먼저 쓰고, 실패하는 걸 확인한 다음 코드를 쓸 것
프롬프트 2: 외부 API를 처음 붙일 때
[API 이름]을 연동해줘. 실제 호출 전에 아래를 먼저 준비해줘.
기본 실행은 아무것도 건드리지 않는 미리보기(dry-run)
실제 호출은 내가 명시적으로 옵션을 붙였을 때만
하루 호출 한도를 코드에 상한으로 두고, 실패한 호출도 한도에 포함
API 키와 응답 원문은 화면·로그·오류 메시지 어디에도 출력하지 말 것
준비가 끝나면 실제 호출은 [대표 항목 1~2개]만, 내 승인을 받고 진행해줘.
프롬프트 3: AI가 만든 결과를 검수시킬 때
방금 구현한 코드를 수정하지 말고 읽기만 해서 검수해줘.
정상 사용자 흐름을 막는 문제만 찾을 것
각 문제는 재현 방법과 실패하는 테스트를 함께 제시할 것
추측이 아니라 실제로 재현되는 것만 보고할 것
희귀한 예외 상황은 별도 목록으로 분리할 것
이번 프로젝트에서 가장 크게 배운 점은, AI에게 코드를 많이 쓰게 하는 것보다 무엇을 믿어도 되는지 검증하는 기준을 만드는 일이 더 중요하다는 것이었습니다. 앞으로도 완벽한 기능 목록보다, 실제로 써보고 발견한 문제를 다음 개발 순서로 삼으려고 합니다.