Vanilla JS × Python × Vercelで株価分析ダッシュボードを個人開発して一般公開するまでにやったこと(と、動的OGPで
Vanilla JS × Python × Vercelで株価分析ダッシュボードを個人開発して一般公開するまでにやったこと(と、動的OGPで挫折した話)
はじめに
「個人投資家でも、機関投資家と同水準の財務分析UIが欲しい」
そんな動機から、Strategic Investment Portal という株価・財務データ可視化ダッシュボードを個人開発し、Vercelで一般公開しました。
この記事では、設計の意思決定から実装の苦労話、そして「動的OGPを断念した」リアルな葛藤まで、開発の全記録を残します。
何を作ったか
日本株20社・米国株・ETFを含む100銘柄の財務データを可視化するSPAです。
主な機能
| 機能 | 詳細 |
|---|---|
| ローソク足チャート | MA5/25/75日線・出来高バーをトグル切替 |
| 財務3表グラフ | BS / PL / CF を Chart.js で可視化 |
| KPIカード | 営業利益率・ROE・PER等を3年度比較 |
| セクターフィルター | 日本株・米国株・ETF・ウォッチリストで絞り込み |
| スクリーニング | PER/PBR/自己資本比率/営業利益率でフィルタ |
| ウォッチリスト | ☆ボタンで登録、localStorageで永続化 |
| CSVエクスポート | 財務3年分のデータをワンクリックで出力 |
| 多通貨対応 | 日本株(JPY/百万円)・米国株(USD/百万ドル)を自動切替 |
技術スタック
フロントエンド : HTML / CSS / Vanilla JS (SPA, 1ファイル構成)
バックエンド : Python (yfinance + EDINET → SQLite)
データ生成 : SQLite → data.js (静的JSONファイル)
デプロイ : Vercel (静的ホスティング)
グラフライブラリ : Lightweight Charts 4.2.3(ローソク足)
Chart.js v4 + chartjs-plugin-datalabels(財務グラフ)
なぜNext.jsではなくVanilla JSにしたのか
最初はNext.js (App Router) での実装を検討しました。しかし今回の要件を整理すると:
- データは静的(バッチ処理で生成したJSファイル)
- APIサーバー不要
- リアルタイム更新不要
「動的なサーバーが不要なら、Vanilla JS + 静的ホスティングが最速」という結論に至りました。Reactのビルドパイプラインを抱えず、index.html 1ファイルで動く構成はデバッグも速く、Vercelへのデプロイも一瞬です。
バックエンド:Pythonパイプラインの構成
yfinance API
↓
auto_terminal_filter.py(データ取得・クレンジング)
↓
weather.db(SQLite)
↓
get_stock_multi.py(data.js生成)
↓
index.html が読み込む
DBテーブル設計
-- 銘柄マスタ
ticker_master (
ticker TEXT,
company_name TEXT,
industry TEXT,
currency TEXT, -- JPY / USD
country TEXT -- JP / US
)
-- 財務データ
financial_data_v2 (
ticker TEXT,
fiscal_year INTEGER,
fiscal_period TEXT,
-- 財務3表の各カラム(売上高・営業利益・純利益 等)
)
日本株と米国株でそれぞれ通貨・国コードを持たせることで、フロントエンド側が通貨バッジや単位(百万円/百万ドル)を自動で切り替えられるようにしています。
フロントエンド:苦労した実装3選
1. BSグラフの「極太バー」問題
Chart.jsの棒グラフで貸借対照表を表現する際、デフォルト設定だとバーが細すぎて財務データが読みにくい問題がありました。
// 解決策: categoryPercentage / barPercentage を両方1.0に
datasets: [{
categoryPercentage: 1.0,
barPercentage: 1.0,
// ...
}]
さらに「Stack0」という仮想データセットを統合することで、積み上げグラフの視認性を大幅に改善しました。
2. データラベルの文字重なり
chartjs-plugin-datalabels でバー上に金額を表示すると、バーの高さによってラベルが重なる問題が発生。
// 役割ごとにanchor/alignを動的に切り替え
formatter: (value, context) => {
// 値が小さい場合は外側、大きい場合は内側に配置
return value > threshold ? value : value;
},
anchor: (context) => context.dataset.data[context.dataIndex] > threshold ? 'end' : 'start'
3. グラフの「見切れ」対策
PL・CFグラフでラベルがキャンバス上部に飛び出す問題。
// layout.paddingでキャンバス上部にスペースを確保
options: {
layout: {
padding: { top: 60 }
}
}
加えて、グラフ生成を150msディレイ後に実行することで、DOMの描画完了タイミングを待つようにしました(これに気づくまでに数時間溶けました)。
断念した機能:動的OGP
「銘柄名をURLパラメータで渡して、OGP画像を動的生成したい」というアイデアがありました。
/dashboard?ticker=7203 → トヨタの財務サマリ画像が生成されてSNSでシェアできる
試したこと
-
Vercel OG (
@vercel/og) → App Router前提のため、静的SPA構成と相性が悪い - Cloudflare Workers + Canvas API → WorkersでCanvas APIが使えない制約に当たった
- Lambda + puppeteer → コールドスタートが重く、無料枠内での運用が難しい
断念の理由と今後
フェーズ1の目標は「財務データの可視化機能を動かすこと」であり、OGP対応に工数をかけすぎるより、コンテンツを充実させる方が優先度が高いと判断しました。
静的なOGP画像(ダッシュボード全体のスクリーンショット)をog:imageに設定する妥協案で現在は対応済みです。
動的OGPについては、フェーズ2でNext.js App Routerへの移行とともに再挑戦する予定です。
デプロイ:VercelへのCI/CD
GitHubリポジトリと連携することで、mainブランチへのpushが自動デプロイに。
git add data.js
git commit -m "データ更新: 2026-05-19"
git push origin main
# → Vercelが自動でビルド・デプロイ
静的ファイルのみの構成なのでビルド時間は数秒。データ更新のたびにこれだけでサイトが最新化されます。
現在のデータ構成
| カテゴリ | 銘柄数 |
|---|---|
| 日本株 | 20社(トヨタ・ソニー・任天堂等) |
| 米国株 | 5社(AAPL・NVDA・MSFT・GOOGL・AMZN) |
| ETF | 5本(SPY等、財務データなし) |
| 合計 | 100銘柄 |
AI分析コメントも全企業・全年度で292件生成済みです。
今後のロードマップ
- SEO対応(sitemap.xml / robots.txt)
- 動的OGP(フェーズ2)
- 複数銘柄比較チャート
- データ更新の自動化(GitHub Actions + cron)
おわりに
個人開発は「完璧を目指して止まる」より「動くものを出して反応を見る」方が絶対に良いと今回改めて実感しました。
動的OGPの断念も、「フェーズ1の範囲ではここまで」と割り切ったことで、他の機能を充実させる時間が生まれました。
もし同じような財務データ可視化ツールを作りたい方や、Vanilla JS + Vercelの静的SPA構成に興味がある方は、コメントでぜひ教えてください!
使用ツール・サービス
- yfinance(株価・財務データ取得)
- SQLite(データ管理)
- Lightweight Charts(ローソク足)
- Chart.js + datalabels(財務グラフ)
- Vercel(ホスティング)
- GitHub(CI/CD)
Discussion