韓国や日本に住みながら米国株を保有すると、ひとつ厄介な問いが生まれます。米国市場が引ける時刻は、KST(韓国標準時。日本時間と同じUTC+9)で土曜の早朝5〜6時。その変動をカレンダーに表示するとき、「米国金曜(現地日付)」の枠に入れるべきでしょうか、それとも「土曜(ユーザーが眠りから覚めた日)」の枠に入れるべきでしょうか。本稿では、この決定が単なる表示の違いではなく、「今日のリターン」が誠実かどうかを左右する問題であることを、コードレベルで解き明かします。
実際のユーザーからの報告で始まった話です。「カレンダーの土曜のセルにSOXL +16.33%がはっきり表示されているのに、合計は−0.49%と出ます。マイナスなのが信じられません。」
SOXLは米国金曜セッションで16%超上昇し、ユーザーはKST(日本時間と同じ)の土曜の朝にそのデータを確認しました。ところがポートフォリオの合計は、まるで米国金曜セッションが存在しなかったかのように、直前の取引日の数値だけを表示していたのです。
この一行の報告が、数日がかりのデータモデル整理につながりました。結論を先に言うと — これは単純なバグではなく、「米国終値をどの日付にマッピングするか」という方針そのものが揺らいでいた問題でした。
Multifoliosの設定には「カレンダー日付基準」というオプションがあります。2つのモード:
| モード | 米国終値をどこに表示 | このモードを使うユーザー |
|---|---|---|
| マーケット基準 (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%の問題は、2箇所で発生していました。
「今日の要約」は今日の日付に該当するカレンダーセルを探して変動率を表示します。週末は市場の活動がないため、直前の金曜に繰り戻して表示するロジックが入っていました。コードは単純でした:
while (lookup.getDay() === 0 || lookup.getDay() === 6) {
lookup.setDate(lookup.getDate() - 1);
}
マーケット基準モードでは、これで正しい。土曜・日曜にはどの市場も新しいデータを生み出しません。しかしKSTモードの土曜は違います。米国金曜セッションがKST土曜にマッピングされているのです。土曜を金曜に戻してしまうと、そのシフトされたデータを取りこぼします。結果: ユーザーが土曜の朝に見た変動率は「米国木曜セッション + 韓国金曜セッション」 — 米国金曜が抜け落ちたマイナスでした。
2つ目はもっと微妙でした。今日の変動率を精密に計算するための別経路があったのです — 各銘柄の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変動までもう一度紛れ込ませてしまいます。
「同じ変動率の式が、モード次第で嘘をつく」 — この気付きが修正の方向を決めました。
2つの原則を立てました。
結果的に、KSTモードはよりシンプルになりました。「今日のKST合計 − 昨日のKST合計」の一行で、すべてのケースが解けます。マーケット基準モードは既存のdirectDayPct経路を維持(韓国・日本のユーザーが米国現地時刻で見たい場合、それが意味を持つため)。
海外製ポートフォリオトラッカーの大半は米国ユーザー基準なので、この問題自体がそもそも浮上しません。米国銘柄 → 米国日付。以上。韓国銘柄があってもマーケット基準で表示して終わりです。
韓国製トラッカーは韓国銘柄だけを深く扱い、米国・日本は補助機能程度のことが多く、「複数市場の終値をKSTカレンダーに一貫して表示する」という要件自体があまり取り上げられません。
MultifoliosがあえてKSTモードを作った理由は — 韓国・日本で米国株を保有するユーザーが実際に生きている時間感覚を、データモデルに反映するためです。米国金曜セッションの急落(あるいは急騰)を土曜の朝のコーヒーとともに確認する人にとって、その変動は土曜の枠にあってこそ誠実なのです。
「今日のリターン」のような些細に見える数字が正確であるためには、そのユーザーがどのタイムゾーンに住み、どの市場を保有しているかをデータモデルが知っている必要があります。一般的なトラッカーが扱わない領域なので、きれいな正解はありません — モードの選択権を与え、各モードで一貫した意味を保証するのが現実的な解法です。
本稿で扱ったデータモデルは、Multifoliosのカレンダーと「今日の要約」にそのまま実装されています。設定 → カレンダー日付基準で2つのモードを切り替えれば、同じ資産がどう異なってマッピングされるかを直接確認できます。