概要
Fear and Greed Indexを毎日自動取得し、このNuxtプロジェクトでチャート表示する実装ガイドです。
アーキテクチャ
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ GitHub Actions │────▶│ JSON データ │────▶│ Nuxt + Chart │
│ (毎日実行) │ │ (蓄積) │ │ (表示) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
利用可能なAPI
1. 仮想通貨版 (Alternative.me) - 無料
# エンドポイント
curl "https://api.alternative.me/fng/?limit=30"
レスポンス例:
{
"name": "Fear and Greed Index",
"data": [
{
"value": "74",
"value_classification": "Greed",
"timestamp": "1733356800"
}
]
}
2. 株式市場版 (CNN) - スクレイピング必要
CNNのFear & Greed Indexは公式APIがないため、以下の方法で取得:
- Python wrapper: fear-greed-index
- FGI Tracker: leejustin/fgi-tracker
- RapidAPI: CNN Fear and Greed Index (有料)
実装手順
Step 1: データ取得スクリプトの作成
scripts/fetch-fear-greed.mjs を作成:
// scripts/fetch-fear-greed.mjs
import fs from 'fs'
import path from 'path'
const DATA_FILE = 'apps/web/public/data/fear-greed-history.json'
async function fetchFearGreedIndex() {
// 仮想通貨版 (Alternative.me) - 無料で使える
const response = await fetch('https://api.alternative.me/fng/?limit=1')
const json = await response.json()
const today = new Date().toISOString().split('T')[0]
const entry = {
date: today,
value: parseInt(json.data[0].value),
classification: json.data[0].value_classification
}
return entry
}
async function main() {
// 既存データを読み込み
let history = []
if (fs.existsSync(DATA_FILE)) {
const raw = fs.readFileSync(DATA_FILE, 'utf-8')
history = JSON.parse(raw)
}
// 新しいデータを取得
const newEntry = await fetchFearGreedIndex()
// 重複チェック(同じ日付があれば更新)
const existingIndex = history.findIndex(h => h.date === newEntry.date)
if (existingIndex >= 0) {
history[existingIndex] = newEntry
} else {
history.push(newEntry)
}
// 日付順にソート
history.sort((a, b) => a.date.localeCompare(b.date))
// 保存
const dir = path.dirname(DATA_FILE)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
fs.writeFileSync(DATA_FILE, JSON.stringify(history, null, 2))
console.log(`Updated: ${newEntry.date} - ${newEntry.value} (${newEntry.classification})`)
}
main().catch(console.error)
Step 2: GitHub Actions ワークフローの作成
.github/workflows/fetch-fear-greed.yml を作成:
name: Fetch Fear & Greed Index
on:
schedule:
# 毎日 UTC 0:00 (日本時間 9:00) に実行
- cron: '0 0 * * *'
workflow_dispatch: # 手動実行も可能
jobs:
fetch:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Fetch Fear & Greed Index
run: node scripts/fetch-fear-greed.mjs
- name: Commit and push changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add apps/web/public/data/fear-greed-history.json
git diff --staged --quiet || git commit -m "📊 Update Fear & Greed Index data"
git push
Step 3: チャートコンポーネントの作成
apps/web/app/components/FearGreedChart.vue を作成:
<template>
<div class="fear-greed-chart">
<h3>Fear & Greed Index</h3>
<div v-if="loading">Loading...</div>
<canvas v-else ref="chartCanvas"></canvas>
<p v-if="latestData" class="latest-value">
最新: {{ latestData.value }} ({{ latestData.classification }})
<span class="date">{{ latestData.date }}</span>
</p>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { Chart, registerables } from 'chart.js'
Chart.register(...registerables)
const chartCanvas = ref(null)
const loading = ref(true)
const latestData = ref(null)
let chartInstance = null
onMounted(async () => {
try {
const response = await fetch('/data/fear-greed-history.json')
const data = await response.json()
// 最新30日分を表示
const recentData = data.slice(-30)
latestData.value = recentData[recentData.length - 1]
chartInstance = new Chart(chartCanvas.value, {
type: 'line',
data: {
labels: recentData.map(d => d.date),
datasets: [{
label: 'Fear & Greed Index',
data: recentData.map(d => d.value),
borderColor: getGradientColor,
backgroundColor: 'rgba(75, 192, 192, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
scales: {
y: {
min: 0,
max: 100,
ticks: {
callback: (value) => {
if (value === 0) return 'Extreme Fear'
if (value === 25) return 'Fear'
if (value === 50) return 'Neutral'
if (value === 75) return 'Greed'
if (value === 100) return 'Extreme Greed'
return value
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: (context) => {
const value = context.parsed.y
return `${value} - ${getClassification(value)}`
}
}
}
}
}
})
} catch (error) {
console.error('Failed to load fear greed data:', error)
} finally {
loading.value = false
}
})
function getClassification(value) {
if (value <= 25) return 'Extreme Fear'
if (value <= 45) return 'Fear'
if (value <= 55) return 'Neutral'
if (value <= 75) return 'Greed'
return 'Extreme Greed'
}
function getGradientColor(context) {
const chart = context.chart
const { ctx, chartArea } = chart
if (!chartArea) return 'rgb(75, 192, 192)'
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top)
gradient.addColorStop(0, 'rgb(220, 53, 69)') // Extreme Fear (赤)
gradient.addColorStop(0.25, 'rgb(255, 193, 7)') // Fear (オレンジ)
gradient.addColorStop(0.5, 'rgb(108, 117, 125)') // Neutral (グレー)
gradient.addColorStop(0.75, 'rgb(40, 167, 69)') // Greed (緑)
gradient.addColorStop(1, 'rgb(0, 123, 255)') // Extreme Greed (青)
return gradient
}
</script>
<style scoped>
.fear-greed-chart {
padding: 20px;
background: var(--color-bg-secondary, #f8f9fa);
border-radius: 8px;
}
.latest-value {
text-align: center;
font-size: 1.2em;
margin-top: 16px;
}
.date {
color: #666;
font-size: 0.8em;
margin-left: 8px;
}
</style>
Step 4: Chart.jsのインストール
cd apps/web
pnpm add chart.js
Step 5: ページでコンポーネントを使用
任意のVueページまたはMarkdownで使用:
<template>
<div>
<h1>市場センチメント</h1>
<FearGreedChart />
</div>
</template>
CNN Fear & Greed Index (株式市場版) を使う場合
株式市場版が必要な場合は、以下の方法があります:
方法A: Python wrapper を使う
# .github/workflows/fetch-fear-greed.yml
jobs:
fetch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install fear-greed-index
- name: Fetch CNN Fear & Greed
run: python scripts/fetch-cnn-fgi.py
# scripts/fetch-cnn-fgi.py
from fear_greed_index import cnn_fgi
import json
from datetime import date
fgi = cnn_fgi.get()
today = date.today().isoformat()
entry = {
"date": today,
"value": fgi.value,
"classification": fgi.description
}
# JSONファイルに追記...
方法B: RapidAPI を使う (有料)
// RapidAPIのキーが必要
const response = await fetch('https://cnn-fear-and-greed-index.p.rapidapi.com/index/fear-greed', {
headers: {
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY,
'X-RapidAPI-Host': 'cnn-fear-and-greed-index.p.rapidapi.com'
}
})
Cloudflare Pagesへのデプロイ時の考慮事項
このプロジェクトはCloudflare Pagesにデプロイされるため、JSONデータファイルは自動的にCDNで配信されます。
- データ更新フロー:
- GitHub Actions → JSONファイル更新 → Git Push
- Cloudflare Pages → 自動ビルド&デプロイ → 新データ反映
- キャッシュ設定 (必要に応じて):
// nuxt.config.ts routeRules: { '/data/**': { headers: { 'Cache-Control': 'public, max-age=3600' // 1時間キャッシュ } } }
まとめ
| 項目 | 内容 |
|---|---|
| データソース | Alternative.me (仮想通貨) または CNN (株式市場) |
| 実行頻度 | 毎日 (GitHub Actions cron) |
| データ形式 | JSON (public/data/fear-greed-history.json) |
| 可視化 | Chart.js (Vue コンポーネント) |
| デプロイ | Cloudflare Pages (自動) |