TradingViewは便利だが、自分が見たい情報だけを詰め込んだ「自分専用のターミナル」が欲しくなり、チャートライブラリに頼らずCanvas APIだけでローソク足描画エンジンから自作した。仮想通貨はBinance、株や商品はTwelve Dataという2つのデータソースを切り替えながら、1枚のHTMLファイルだけで完結する構成にしている。今回はその設計と、実装でつまずいた部分の記録。
なぜチャートライブラリを使わずCanvasで自作したか
既存のチャートライブラリを組み込めば早いのは分かっていたが、表示ロジックを細部まで自分で制御したかったのと、単純に「ローソク足チャートを自分の手で描く」という仕組み自体を理解したかったのが動機。結果として、外部ライブラリへの依存なしに1ファイルのHTMLだけで完結する構成になり、そのまま配布・移植しやすいというメリットも生まれた。
全体構成
- 描画:Vanilla JS+Canvas API(外部チャートライブラリ不使用)
- データソース:仮想通貨はBinanceのAPI、株・商品はTwelve DataのAPI
- フォールバック:APIキー未設定やレート制限時は、デモ用の擬似データに自動切り替え
- ファイル構成:HTML・CSS・JSを1ファイルに集約し、単体で動作
ローソク足を描く基本ロジック
Canvasでのローソク足描画は、要するに「価格をY座標に変換する関数」と「1本1本のローソクを矩形と線で描く関数」の組み合わせで成り立っている。骨格はこんな形になる。
function priceToY(price, minPrice, maxPrice, canvasHeight) {
return canvasHeight - ((price - minPrice) / (maxPrice - minPrice)) * canvasHeight;
}
function drawCandle(ctx, x, candleWidth, open, close, high, low, minPrice, maxPrice, canvasHeight) {
const yOpen = priceToY(open, minPrice, maxPrice, canvasHeight);
const yClose = priceToY(close, minPrice, maxPrice, canvasHeight);
const yHigh = priceToY(high, minPrice, maxPrice, canvasHeight);
const yLow = priceToY(low, minPrice, maxPrice, canvasHeight);
const isUp = close >= open;
ctx.strokeStyle = isUp ? '#26a69a' : '#ef5350';
ctx.fillStyle = isUp ? '#26a69a' : '#ef5350';
// ヒゲ
ctx.beginPath();
ctx.moveTo(x + candleWidth / 2, yHigh);
ctx.lineTo(x + candleWidth / 2, yLow);
ctx.stroke();
// 実体
const bodyTop = Math.min(yOpen, yClose);
const bodyHeight = Math.max(Math.abs(yClose - yOpen), 1);
ctx.fillRect(x, bodyTop, candleWidth, bodyHeight);
}
実際にはこれに加えて、可視範囲の価格帯からmin/maxを毎フレーム再計算する処理、横スクロールに応じて表示するローソクの範囲を絞り込む処理が必要になる。
複数データソースの統合で苦労した点
BinanceとTwelve Dataでは、レスポンスの形式もレート制限の考え方もまったく違う。そのままハンドリングすると描画側のコードが分岐だらけになるので、内部的には両方とも同じ形(時刻・始値・高値・安値・終値・出来高の共通オブジェクト)に変換してから描画関数に渡す設計にした。データソースが増えても描画側のコードを変えずに済むのがこの設計のメリット。
function normalize(raw, source) {
if (source === 'binance') {
return { time: raw[0], open: +raw[1], high: +raw[2], low: +raw[3], close: +raw[4], volume: +raw[5] };
}
if (source === 'twelvedata') {
return { time: raw.datetime, open: +raw.open, high: +raw.high, low: +raw.low, close: +raw.close, volume: +raw.volume };
}
}
※フィールド名は各APIの仕様変更で変わることがあるので、実装時は必ず最新のドキュメントで確認したうえで調整してほしい。
デモフォールバックの設計
APIキーが未設定だったり、レート制限に引っかかったりした場合に画面が真っ白になるのを避けたかったので、その場合は擬似的なランダムウォークで生成したデモデータに自動的に切り替わる仕組みを入れている。実データかデモデータかを画面上に明示しておくことで、見た人が誤解しないようにしている。
パフォーマンス面の工夫
ティックが来るたびに画面全体を再描画すると重くなるので、価格更新時のみ再描画をトリガーし、それ以外はrequestAnimationFrameでスクロール・ズーム操作の反映だけを行うようにした。ローソクの本数が増えてきたときは、画面に映る範囲だけを描画対象にする間引き処理も入れている。
今後の展望
- 以前作ったFRVP(出来高プロファイル)をこのターミナルに統合する
- 複数時間足の切り替え対応
- ウォッチリスト機能の追加
まとめ
チャートライブラリなしでローソク足描画から組むのは手間がかかる反面、価格→座標変換とデータ正規化さえ押さえてしまえば、あとは機能を積み増していくだけの土台になる。次はFRVPとの統合について書く予定。