한국에서 미국 주식을 보유하면 한 가지 곤란한 질문이 생깁니다. 미국장이 마감되는 시각은 한국 시간으로 토요일 새벽 5~6시. 그 변동을 캘린더에 표시할 때 "미국 금요일 (현지 날짜)" 칸에 넣어야 할까요, 아니면 "한국 토요일 (사용자의 잠에서 깨어난 날)" 칸에 넣어야 할까요? 이 글은 그 결정이 단순한 표시 차이가 아니라 "오늘의 수익률"이 정직한지 여부를 결정한다는 점을 코드 레벨에서 풀어봅니다.
실제 사용자 제보로 시작된 이야기입니다. "캘린더의 토요일 셀에 SOXL +16.33% 가 분명히 표시되는데 합계는 -0.49% 로 나옵니다. 마이너스인 게 믿기지 않아요."
SOXL 은 미국 금요일 세션에 16% 넘게 올랐고, 사용자는 한국 시간 토요일 아침에 그 데이터를 확인했습니다. 하지만 포트폴리오 합계는 마치 미국 금요일 세션이 없었던 것처럼 직전 거래일의 수치만 보여주고 있었죠.
이 한 줄 제보가 며칠에 걸친 데이터 모델 정리로 이어졌습니다. 결론을 미리 말하면 — 이건 단순한 버그가 아니라 "미국 종가를 어느 날짜에 매핑하느냐"의 정책 자체가 흔들리고 있던 문제였습니다.
멀티폴리오스의 설정에는 "캘린더 날짜 기준" 이라는 옵션이 있습니다. 두 가지 모드:
| 모드 | 미국 종가를 어디에 표시 | 이 모드를 쓰는 사용자 |
|---|---|---|
| 시장 기준 (Market) | 미국 현지 날짜. Yahoo Finance 원본 그대로. | 미국 증권사를 직접 쓰는 사용자, 미국 현지 보도와 일치시키고 싶을 때. |
| KST 기준 | 미국 종가를 +1일 시프트. 미국 금요일 → KST 토요일. | 한국·일본에서 미국 주식을 보유한 사용자. "잠에서 깬 그 날" 의 변동을 보고 싶을 때. |
기본값은 한국어 사용자에게 KST. 일본어·영어 사용자에게 Market. 이 선택은 단순한 표시 옵션이 아닙니다. 캘린더 셀의 숫자, 자산 추이 그래프의 단계, "오늘의 요약" 의 일일 변동률 — 이 모든 것이 같은 데이터를 어떤 날짜에 매핑하느냐에 따라 달라집니다.
캘린더 스냅샷을 빌드하는 함수는 다음과 같은 흐름으로 동작합니다:
// pseudocode
for each holding:
history = fetchYahooHistory(symbol) // 일별 종가 배열
for each (date, close) in history:
if mode === 'kst' && isUSSymbol(symbol):
// 미국 종목만 +1일 시프트
priceMap[symbol][shiftByOne(date)] = close
else:
priceMap[symbol][date] = close
for each calendar day d:
for each holding:
price = priceMap[symbol][d] || forwardFill(...)
total += price * shares
snapshot[d] = total
핵심: shiftByOne 은 미국 종목에만 적용됩니다. 한국·일본 종목은 그대로. 이 비대칭이 KST 모드의 본질입니다.
구체적으로 따라가 봅시다. 2026년 5월 8일 (미국 금요일):
사용자가 보고한 -0.49% 문제는 두 군데에서 발생했습니다.
"오늘의 요약" 은 오늘 날짜에 해당하는 캘린더 셀을 찾아 변동률을 표시합니다. 주말에는 시장 활동이 없으니 직전 금요일로 굴려 표시하는 로직이 들어가 있었죠. 코드는 단순했습니다:
while (lookup.getDay() === 0 || lookup.getDay() === 6) {
lookup.setDate(lookup.getDate() - 1);
}
시장 기준 모드에서는 이게 맞습니다. 토요일·일요일에는 어느 시장도 새 데이터를 안 만들어요. 하지만 KST 모드의 토요일은 다릅니다. 미국 금요일 세션이 KST 토요일에 매핑되어 있죠. 토요일을 금요일로 돌려버리면 그 시프트된 데이터를 놓치게 됩니다. 결과: 사용자가 토요일 아침에 본 변동률은 "미국 목요일 세션 + 한국 금요일 세션" — 미국 금요일이 빠진 마이너스.
두 번째는 더 미묘했습니다. 오늘 날짜의 변동률을 정밀하게 계산하기 위해 별도 경로가 있었습니다 — 각 종목의 currentPrice 와 prevClose 를 직접 빼서 합산하는 방식. 이게 평소엔 정확하지만, KST 모드 토요일에서는 잘못된 답을 줍니다.
왜? Yahoo Finance API 가 알려주는 regularMarketPreviousClose 는 각 종목 자체의 직전 거래 세션 종가입니다. SOXL 의 경우 토요일에 조회하면 prevClose = 미국 목요일 종가. 그런데 KST 토요일 스냅샷은 이미 "미국 금요일 종가" 를 들고 있어서, currentPrice (Fri) − prevClose (Thu) = 금요일 세션 변동을 over-count 합니다.
한국 종목은 더 심각합니다. KST 토요일에 삼성의 currentPrice = 금요일 종가, prevClose = 목요일 종가. 그런데 KST 5/9 스냅과 KST 5/8 스냅 둘 다 한국 금요일 종가를 forward-fill 로 들고 있으니, 실제로 한국 입장에서 5/9 → 5/8 변동은 0 이어야 합니다. directDayPct 는 한국 Fri-Thu 변동까지 한 번 더 끼워 넣어버립니다.
"같은 변동률 공식이 모드에 따라 거짓을 말한다." — 이 깨달음이 수정의 방향을 정했습니다.
두 가지 원칙을 세웠습니다.
결과적으로 KST 모드는 더 단순해졌습니다. "오늘 KST 총합 − 어제 KST 총합" 한 줄로 모든 케이스가 풀립니다. 시장 기준 모드는 기존 directDayPct 경로를 유지 (한국·일본 사용자가 미국 현지 시각으로 보고 싶을 때 그게 의미 있음).
대부분의 외산 포트폴리오 트래커는 미국 사용자 기준이라 이 문제가 아예 안 떠오릅니다. 미국 종목 → 미국 날짜. 끝. 한국 종목이 있어도 시장 기준으로 표시하고 끝나죠.
한국 트래커는 종종 한국 종목만 깊이 다루고 미국·일본은 보조 기능 정도여서, "여러 시장의 종가를 KST 캘린더에 일관되게 표시" 라는 요구 자체가 잘 다뤄지지 않습니다.
멀티폴리오스가 KST 모드를 굳이 만든 이유는 — 한국·일본에서 미국 주식을 보유한 사용자가 실제로 사는 시간 감각을 데이터 모델에 반영하기 위해서입니다. 미국 금요일 세션의 폭락 (또는 폭등) 을 토요일 아침 커피와 함께 확인하는 사람에게, 그 변동이 토요일 칸에 있어야 정직합니다.
"오늘의 수익률" 같이 사소해 보이는 숫자가 정확하려면, 그 사용자가 어느 시간대에서 어떤 시장을 보유하고 있는지를 데이터 모델이 알아야 합니다. 일반 트래커가 다루지 않는 영역이라서 깔끔한 정답이 없습니다 — 모드 선택권을 주고, 각 모드에서 일관된 의미를 보장하는 게 현실적 해법입니다.
이 글에서 다룬 데이터 모델은 멀티폴리오스 캘린더와 "오늘의 요약" 에 그대로 들어가 있습니다. 설정 → 캘린더 날짜 기준에서 두 모드를 바꿔보면 같은 자산이 어떻게 다르게 매핑되는지 직접 확인할 수 있습니다.