• #GitHub Actions
  • #自動化
  • #Fear and Greed Index
  • #Chart.js
  • #投資

概要

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がないため、以下の方法で取得:


実装手順

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で配信されます。

  1. データ更新フロー:
    • GitHub Actions → JSONファイル更新 → Git Push
    • Cloudflare Pages → 自動ビルド&デプロイ → 新データ反映
  2. キャッシュ設定 (必要に応じて):
    // 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 (自動)

参考リンク