
リアルタイム相場API実践:WebSocketでリアルタイム相場監視パネルを構築する
WebSocketとNode.jsを使用して、リアルタイム株価監視ダッシュボードを構築する方法を解説します。複数銘柄の購読、リアルタイム価格更新、データ転送、再接続処理について紹介します。
株式相場の監視パネルにAPIを接続したのに、なぜ継続的に更新される銘柄がある一方で、古い価格のまま更新されない銘柄もあるのでしょうか。監視対象が2~3銘柄から数十銘柄に増えると、相場更新の不整合、購読設定の上書き、ネットワーク切断後にデータ更新が停止するといった問題が徐々に表面化することがあります。
こうした問題は、必ずしも株式相場API自体の障害とは限りません。WebSocketの購読方法、データ処理ロジック、フロントエンドの更新方式に原因がある可能性もあります。
最新の株価を継続的に表示する金融アプリケーションでは、HTTPリクエストを繰り返して取得するよりも、WebSocketでリアルタイム相場データを受信する方法が適しています。本記事では、AllTickのリアルタイム相場APIを例に、Node.jsで相場データ受信サービスを構築し、そのデータをフロントエンドの監視パネルに渡す方法を紹介します。特に、複数銘柄の購読、相場データの更新、接続復旧という3つの課題に焦点を当てます。
複数銘柄の相場データはなぜ更新タイミングがずれやすいのか?
よくあるケースを考えてみましょう。ある株式監視ページで、700.HK、1288.HK、その他の銘柄の最新価格を同時に表示するとします。開発者が定期的なリクエストで相場データを取得すれば、ページは正常に表示されます。しかし、監視対象の銘柄が増えるにつれて、リクエスト数も増加します。
WebSocketに切り替えると、クライアントは接続を確立して銘柄を購読した後、サーバーから新しい約定データを継続的にプッシュしてもらえます。価格が変化するたびにHTTPリクエストを再送信する必要はありません。
2種類のAPIは、次のように役割を分けることができます。
| データのニーズ | 適したインターフェース | 主な用途 |
|---|---|---|
| 最新約定価格 | WebSocket | ティック単位の相場データを継続的に受信 |
| 売買板の深度 | WebSocket | 板情報の変化を表示 |
| 過去のK線データ | REST API | 過去の価格推移を読み込む |
| 初期相場データの取得 | REST API | ページの初期データを読み込む |
注意したいのは、WebSocketを利用すればページが自動的に更新されるわけではないという点です。サーバーが相場データを受信した後、データの解析、キャッシュ、フロントエンドへの転送を行う必要があります。これらの処理が適切でなければ、APIが新しい価格を受信していても、ブラウザには古いデータが表示され続ける可能性があります。
WebSocket APIで複数銘柄を購読する
AllTickは株式市場向けに専用のWebSocket接続アドレスを提供しています。接続時には、有効なTokenをアドレスに含める必要があります。
最新約定価格の購読にはプロトコル番号22004を使用し、購読応答には22005を使用します。サーバーは22998で最新の約定データをプッシュします。
まず、Node.jsのWebSocket依存パッケージをインストールします。
npm install wsAPI Tokenをサーバー側の環境変数ALLTICK_API_TOKENに設定し、接続を確立します。
const WebSocket = require("ws");
const crypto = require("crypto");
const token = process.env.ALLTICK_API_TOKEN;
if (!token) {
throw new Error("Missing API Token");
}
const symbols = [
{ code: "700.HK" },
{ code: "1288.HK" }
];
let ws;
let reconnectTimer;
let heartbeatTimer;
let stopped = false;
function send(data) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
function subscribe() {
send({
cmd_id: 22004,
seq_id: 1,
trace: crypto.randomUUID(),
data: {
symbol_list: symbols
}
});
}
function connect() {
ws = new WebSocket(
`wss://quote.alltick.co/quote-stock-b-ws-api?token=${encodeURIComponent(token)}`
);
ws.on("open", () => {
console.log("WebSocket connected");
subscribe();
// ハートビートメッセージは、最新の公式ハートビートプロトコルに従って送信する必要があります
// 個別に確認できていないハートビートメッセージを、検証済みの実装として記載しないようにしています
});
ws.on("message", (raw) => {
try {
const message = JSON.parse(raw.toString());
if (message.cmd_id === 22005) {
if (message.ret !== 200) {
console.error("Subscription failed", message.msg);
} else {
console.log("Subscription successful");
}
return;
}
if (message.cmd_id === 22998) {
updateQuote(message.data);
}
} catch (error) {
console.error("Message parsing failed", error);
}
});
ws.on("close", () => {
console.log("WebSocket disconnected");
clearInterval(heartbeatTimer);
if (!stopped && !reconnectTimer) {
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
connect();
}, 10000);
}
});
ws.on("error", (error) => {
console.error("WebSocket error:", error.message);
});
}
function updateQuote(tick) {
const quote = {
code: tick.code,
price: Number(tick.price),
volume: Number(tick.volume),
tickTime: tick.tick_time
};
console.log("Latest quote:", quote);
// ここで相場データのキャッシュを更新し、フロントエンドに転送する
}
connect();このコードは、接続、購読、メッセージ解析、切断後の再接続という基本的な処理を示しています。相場データ受信サービスの中核となるサンプルであり、本番環境向けの完全なプロジェクトではありません。実際に運用する際は、公式ハートビートプロトコルに従った定期的なハートビート送信と、サーバーからブラウザへの相場データ転送処理を追加する必要があります。
特に注意したいのは、購読応答と相場データのプッシュは異なる種類のメッセージだという点です。22005は購読リクエストが成功したかどうかを確認するためのもので、実際の最新約定価格データは22998でプッシュされます。購読成功の応答を受信しただけで、相場データを継続的に受信できていると判断してはいけません。
相場データの受信後、監視パネルをどのように更新するか?
相場データがNode.jsサービスに届いたら、まず銘柄コードごとに最新の価格情報を保存し、そのデータをブラウザに転送する方法がおすすめです。
例えば、フロントエンドではMapを使って最新の相場データを管理できます。
const latestQuotes = new Map();
function updateQuote(tick) {
latestQuotes.set(tick.code, {
price: Number(tick.price),
volume: Number(tick.volume),
tickTime: tick.tick_time
});
renderQuote(tick.code, latestQuotes.get(tick.code));
}
renderQuote()は、監視パネル全体を再描画するのではなく、該当する銘柄の表示領域だけを更新します。
例えば、ページ内にテンセントの株価を表示する領域がある場合、銘柄コードを使って対応する要素を取得できます。
function renderQuote(code, quote) {
const row = document.getElementById(`quote-${code}`);
if (!row) return;
row.querySelector(".price").textContent =
quote.price.toFixed(2);
row.querySelector(".volume").textContent =
quote.volume;
row.querySelector(".time").textContent =
quote.tickTime;
}
フロントエンドでは、対応するHTML要素をあらかじめ用意しておく必要があります。
<div id="quote-700.HK">
<span class="price">--</span>
<span class="volume">--</span>
<span class="time">--</span>
</div>ここでは、tickTimeをひとまず元の値のまま表示しています。公式のフィールド説明とサンプルに記載されたタイムスタンプ形式は、現在のAPIバージョンに照らして確認する必要があります。実際のプロジェクトでは、単位を確認してから日時に変換してください。
もう一つ重要なのは、アーキテクチャ上の問題です。ブラウザ側にサーバー用API Tokenを直接保存すべきではありません。より安全な方法は、Node.jsサーバーからAllTickに接続し、独自のWebSocketサービスを通じて相場データをフロントエンドに転送することです。これにより、認証情報を保護できるだけでなく、複数ページの購読も一元管理しやすくなります。
複数銘柄を購読するときに見落としやすい点
監視パネルに表示する銘柄が増えると、購読ルールと接続管理が相場データの継続的な更新に直接影響します。
購読リストは自動的に追加されるわけではありません。
これはAllTickの株式WebSocketを利用する際に、特に注意すべき点です。
例えば、最初に700.HKと1288.HKを購読し、その後にAAPL.USを追加したい場合、新しい銘柄コードだけを送信してはいけません。同じWebSocket接続で再度購読リクエストを送信すると、新しいリストが以前の購読リストを上書きします。
正しい方法は、完全なリストを再送信することです。
const symbols = [
{ code: "700.HK" },
{ code: "1288.HK" },
{ code: "AAPL.US" }
];これにより、銘柄を追加した後に既存銘柄のデータ配信が意図せず停止することを防げます。
購読数は契約プランに合わせる必要があります。
AllTickでは、最新約定価格を同時に購読できる銘柄数がプランごとに異なります。
| プラン | 同時購読可能な銘柄数の上限 |
|---|---|
| Free | 5 |
| Basic | 100 |
| Premium | 200 |
| Professional | 3000 |
上記はティック単位の約定データ購読数の上限です。各プランでWebSocket接続数を無制限に増やせるという意味ではありません。実際の導入時には、接続数とアカウント権限も考慮する必要があります。
購読リクエストを頻繁に送信しすぎないことも重要です。
AllTickのWebSocket制限に関する説明では、リクエスト間隔についても明確な要件が定められています。
- 同じWebSocket接続内では、異なるインターフェースへのリクエスト間に少なくとも1秒の間隔を空ける必要があります。
- 複数のWebSocket接続間では、購読リクエストの送信間隔を少なくとも3秒空ける必要があります。
- 購読が成功した後は、購読リクエストを繰り返し送信する必要はありません。インターフェースの要件に従ってハートビートを維持してください。
そのため、監視パネルで銘柄の動的な追加に対応する場合は、ローカルの購読リストを先に更新し、その後、完全な購読リストをまとめて送信することをおすすめします。ユーザーが銘柄をクリックするたびに、複数のリクエストをすぐに送信する設計は避けましょう。
相場データの更新が停止した場合、どのように原因を調べるか?
監視パネルで見落とされやすい問題の一つは、接続が切断された後も、ページに最後に受信した価格が表示されたままになることです。
画面に価格が表示されているからといって、相場データが正常に更新されているとは限りません。監視パネルには、接続状態、最終更新時刻、最新約定価格を併せて表示することをおすすめします。
次の順番で確認できます。
| 発生している現象 | 優先して確認する項目 |
|---|---|
| すべての銘柄の更新が停止した | WebSocket接続状態、ハートビート、ネットワーク |
| 一部の銘柄だけ更新が停止した | 銘柄コード、購読範囲、取引状態 |
| 銘柄を追加した後、既存銘柄が更新されなくなった | 完全な購読リストを再送信したか |
| サーバーはデータを受信しているが、ページが変化しない | キャッシュの更新、フロントエンドへのメッセージ転送 |
| 再接続後も相場データが復旧しない | 購読リクエストを再送信したか |
自動再接続を実装する場合は、接続復旧後のデータ状態も考慮する必要があります。接続を再確立して再購読すれば、その時点以降の相場データ配信は再開できますが、切断中に受信できなかったティックデータが自動的に補完されるわけではありません。
完全な過去データや連続したバックテスト用データが必要な場合は、対応する過去データAPIを別途利用して取得する必要があります。WebSocketの再接続を、過去データの復元手段として扱ってはいけません。
リアルタイム相場APIはどのように組み合わせるべきか?
最新株価と過去の価格推移を表示する監視パネルでは、REST APIとWebSocket APIを役割ごとに使い分ける設計が適しています。
REST APIはページの初期化と過去のK線データの取得を担当し、WebSocket APIは最新約定価格を継続的に受信する役割を担います。両者を組み合わせることで、最新価格を維持するために頻繁なポーリングを行う必要がなくなり、ティック単位のプッシュデータで過去の相場データを代用する必要もありません。
AllTickは株式のリアルタイム約定価格と板情報の購読機能を提供しています。開発者は監視パネルの実際のニーズに応じて、対応するインターフェースを選択できます。なお、最新約定価格の購読は板情報の購読と同じものではなく、株式向けWebSocket APIで過去のK線データ取得を直接代替することもできません。
株式相場の監視パネルを開発している場合は、まず少数の銘柄から始め、購読応答、ティックデータのプッシュ、データ更新、切断後の復旧を確認してから、プランの制限に合わせて監視対象を増やすとよいでしょう。
詳しくは、以下のドキュメントをご確認ください。
- AllTick 株式最新約定価格 WebSocket購読ドキュメント:購読プロトコル、プッシュフィールド、完全なリクエスト例を確認できます。
- AllTick 株式板情報 WebSocket購読ドキュメント:売買板の深度データを購読する方法を確認できます。
- AllTick WebSocketインターフェース制限の説明:購読数、リクエスト間隔、接続制限を確認できます。