💹 네비게이션 바 아래로 주가가 흘러간다
네비게이션 바 바로 아래에 TSLA, AAPL, QQQ, SPY 가격이 좌에서 우로 끊임없이 흘러가는 티커를 달았다. Bloomberg 같은 금융 사이트에서 보던 그 느낌이다.
살아있는 데이터가 있는 사이트는 분위기가 완전히 다르다. Alpha Vantage 무료 API를 썼다. 신용카드 없이, 이메일 하나로 시작할 수 있다.
주가 데이터는 실시간이 아니라 15~20분 지연된 데이터다. 무료 플랜 기준이며 개인 사이트 분위기용으로는 충분하다.
📊 Alpha Vantage가 뭔가요?
주식, ETF, 암호화폐 등 금융 데이터를 API로 제공하는 서비스다. 전 세계 개발자들이 개인 프로젝트에 많이 쓴다.
- 무료 플랜 — 신용카드 없이 이메일만으로 가입 가능
- 분당 5회 · 월 500회 호출 — 개인 사이트 분위기용으로는 충분
- 미국 주식, ETF, 외환, 암호화폐 데이터 제공
- JSON 형식으로 응답 — JavaScript로 바로 파싱 가능
월 500회 호출은 생각보다 빨리 소진된다. 4개 종목을 하루 4회씩 갱신하면 하루 16회. 한 달이면 480회. 캐싱 없이 페이지 로드 때마다 호출하면 금방 소진된다. 반드시 캐싱을 함께 적용해야 한다.
🔑 API 키 발급 방법
이메일 인증도 없다. 폼 하나 채우면 바로 발급된다.
🚨 분당 5회 제한 — 실제로 겪은 문제
처음에 TSLA, AAPL, QQQ, SPY 네 종목을 페이지 로드 시 동시에 호출했다. 결과는 rate limit 오류였다. 숫자 대신 이런 문자열이 응답으로 왔다.
주가인 줄 알고 화면에 표시했더니 숫자 대신 긴 영어 문장이 떴다. JSON 파싱에서 걸러내지 않으면 그대로 노출된다.
해결법 1 — 순차 호출 (1.5초 간격)
네 종목을 동시에 호출하지 않고 1.5초 간격으로 순서대로 호출한다. 4개 종목 기준 총 6초면 모두 로드된다. 체감상 느리지 않다.
const SYMBOLS = ['TSLA', 'AAPL', 'QQQ', 'SPY'];
const API_KEY = '여기에_발급받은_키_입력';
const DELAY = 1500; // 1.5초 간격
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function loadAllTickers() {
for (let i = 0; i < SYMBOLS.length; i++) {
if (i > 0) await delay(DELAY);
await fetchQuote(SYMBOLS[i]);
}
}
async function fetchQuote(symbol) {
const url = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${symbol}&apikey=${API_KEY}`;
const res = await fetch(url);
const data = await res.json();
const q = data['Global Quote'];
// rate limit 응답 감지 — 주가 데이터가 없으면 건너뜀
if (!q || !q['05. price']) return;
const price = parseFloat(q['05. price']).toFixed(2);
const change = parseFloat(q['10. change percent']).toFixed(2);
renderTicker(symbol, price, change);
}
해결법 2 — localStorage 캐싱
10분 이내에 같은 사람이 재방문하면 API를 다시 호출하지 않고 저장된 데이터를 쓴다. 호출 횟수를 크게 줄여준다.
const CACHE_KEY = 'ticker_cache';
const CACHE_TTL = 10 * 60 * 1000; // 10분
function getCache() {
try {
const raw = localStorage.getItem(CACHE_KEY);
if (!raw) return null;
const { data, ts } = JSON.parse(raw);
if (Date.now() - ts > CACHE_TTL) return null; // 만료
return data;
} catch { return null; }
}
function setCache(data) {
localStorage.setItem(CACHE_KEY, JSON.stringify({ data, ts: Date.now() }));
}
// 로드 시 캐시 확인 → 없으면 API 호출
const cached = getCache();
if (cached) {
renderFromCache(cached);
} else {
loadAllTickers();
}
🪜 만드는 순서 — AI한테 이렇게 시켜라
직접 코드를 짤 필요 없다. AI한테 아래 프롬프트를 그대로 넣으면 된다.
"Alpha Vantage API로 주가 티커 만들어줘. TSLA, AAPL, QQQ, SPY 네 종목을 보여주고, 상승은 초록색 ▲, 하락은 빨간색 ▼로 표시해줘. 티커가 네비게이션 바 바로 아래에서 좌→우로 무한 흘러가게 해줘. 분당 5회 제한 때문에 종목별로 1.5초 간격 순차 호출하고 localStorage로 10분 캐싱도 적용해줘. API 키는 [키값]이야."
이 프롬프트 하나면 HTML 구조, CSS 애니메이션, JS 호출 로직, 캐싱까지 한 번에 완성 코드가 나온다.
📌 네비게이션 바에 붙이기 — 위치 잡는 게 까다로웠다
처음에 티커를 nav 태그 안에 넣었더니 메뉴 항목들과 같은 줄에 나란히 붙어버렸다.
이유는 nav가 flex 레이아웃이기 때문이다. 안에 넣는 요소는 자동으로 flex 아이템이 돼서 메뉴 옆으로 밀려 들어간다.
display: flex로 설정된 경우가 많다. 그 안에 티커 div를 넣으면 로고·메뉴·버튼과 같은 줄에 나란히 놓인다. 너비를 100%로 줘도 flex 아이템 특성상 한 줄을 독점하지 못한다.
</nav> 다음 줄에 <div class="ticker-bar">를 배치하면 자연스럽게 nav 아래 전체 너비를 차지한다.
position: sticky; top: 68px;를 적용한다. 68px은 nav의 높이다. nav가 sticky로 고정돼 있으니 그 바로 아래에 붙어서 함께 따라오게 된다. z-index는 nav보다 하나 낮게 설정해야 드롭다운 메뉴가 티커 위로 올라온다.
<!-- nav 닫는 태그 -->
</nav>
<!-- 티커: nav와 완전히 분리된 별도 블록 -->
<div class="ticker-bar">
<div class="ticker-track">
<!-- 종목 항목들 -->
</div>
</div>
<!-- 그 다음 header(히어로) -->
<header class="hero">...
.ticker-bar {
position: sticky;
top: 68px; /* nav 높이만큼 밀어서 nav 바로 아래에 고정 */
z-index: 99; /* nav(100)보다 1 낮게 → 드롭다운 메뉴가 위로 올라옴 */
background: #0f1117;
overflow: hidden;
height: 38px;
}
✅ 마무리 — 분당 5회만 알면 바로 된다
Alpha Vantage 무료 API의 핵심은 분당 5회 제한이다. 이것만 알고 순차 호출과 캐싱을 함께 적용하면 오류 없이 바로 작동한다.
네비게이션 바 위치 배치도 처음엔 nav 안에 넣어서 삽질했지만,
</nav> 아래에 별도 div로 분리하면 깔끔하게 해결된다.
주가 티커 하나로 사이트 분위기가 확 달라진다. 직접 해보면 안다.
이 글이 도움이 됐나요?
주가 티커 구현부터 다양한 외부 API 연동까지 교재에서 단계별로 확인할 수 있어요.
📖 뚝딱닷컴 교재 보기