【ChatGPT開発入門】FANG+のドル建て、円建て価格のプログラム開発🎵

## FANG+とは
FANG+（ファングプラス）は、米国のテクノロジー分野で注目される企業群の総称です。FANGは以下の企業の頭文字を取ったものです。

- **F**: Facebook（現Meta Platforms, Inc.）
- **A**: Amazon.com, Inc.
- **N**: Netflix, Inc.
- **G**: Google（現Alphabet Inc.）

これに加えて、テクノロジー業界の他の有力企業を含めると、FANG+という拡張されたグループが形成されます。FANG+に含まれる企業には以下のものがあります。

- **Meta Platforms, Inc.（META）**
- **Amazon.com, Inc.（AMZN）**
- **Apple Inc.（AAPL）**
- **Netflix, Inc.（NFLX）**
- **Alphabet Inc.（GOOGL）**
- **Tesla, Inc.（TSLA）**
- **NVIDIA Corporation（NVDA）**
- **Microsoft Corporation（MSFT）**
- **Twitter, Inc.（X）**
- **Alibaba Group Holding Limited（BABA）**

これらの企業は、市場において高い成長率と技術革新で注目されています。この記事では、FANG+企業のドル建て、円建て価格のプログラム開発について解説します。

## プログラムの概要
FANG+企業の株価を取得し、それをドル建てと円建ての形式で表示するプログラムを開発します。このプログラムは、以下のような機能を持っています。

- ユーザーが指定した期間の株価データを取得
- 取得した株価データを規格化し、指定した日付の基準値に合わせて調整
- ドル建て株価と円建て株価、およびドル円の為替レートをグラフ表示

## 開発の注意点
プログラムを開発する際の注意点を以下にまとめます。

### 1. データ取得の信頼性
株価データの取得には、信頼性の高いデータソースを使用することが重要です。この記事では、Yahoo FinanceのAPI（`yfinance`）を使用します。ただし、データソースの選定に際しては、取得できるデータの範囲や精度について事前に確認しましょう。

### 2. データの整形と前処理
取得した株価データを適切に整形し、必要な形式に変換することが重要です。特に、日付のフォーマットやデータのマージには注意が必要です。

### 3. エラーハンドリング
データの取得や処理の過程でエラーが発生する場合があります。これを適切に処理し、ユーザーに分かりやすいエラーメッセージを表示することが重要です。

### 4. 為替レートの取得
円建て株価を計算するために、ドル円の為替レートを取得する必要があります。為替レートも信頼性の高いデータソースから取得しましょう。

### 5. グラフの表示
取得したデータをグラフで表示する際には、見やすさと直感的な操作性を重視します。特に、時間軸の表示や縦軸のスケール調整には工夫が必要です。


## プログラムのディレクトリ構成
以下はプログラムのディレクトリ構成の例です。

```
fang_plus_app/
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   └── requirements.txt
│   └── Dockerfile
├── frontend/
│   ├── pages/
│   │   └── index.js
│   ├── public/
│   ├── styles/
│   ├── next.config.js
│   └── package.json
├── .gitignore
└── README.md
```

## 必要なライブラリのインストール
### バックエンド（FastAPI）のインストール
まず、`requirements.txt`に必要なライブラリを記載します。

```
fastapi
uvicorn
pandas
numpy
yfinance
logging
```

次に、これらのライブラリをインストールします。

```bash
pip install -r requirements.txt
```

### フロントエンド（Next.js）のインストール
`package.json`に必要な依存関係を追加します。

```json
{
  "name": "fang-plus-frontend",
  "version": "1.0.0",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "latest",
    "react": "latest",
    "react-dom": "latest",
    "chart.js": "latest",
    "react-chartjs-2": "latest",
    "chartjs-adapter-date-fns": "latest",
    "date-fns": "latest"
  }
}
```

次に、これらの依存関係をインストールします。

```bash
npm install
```

### プログラムの実装
以下に、FastAPIを使用したバックエンドのコードと、Next.jsを使用したフロントエンドのコードの例を示します。

```python/main.py
# app/main.py

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import yfinance as yf
import pandas as pd
import numpy as np
import logging

app = FastAPI()

origins = [
    "http://localhost:3000",  # Next.jsのデフォルトのポート
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# FANG+のティッカーシンボルリスト（新しいシンボルを含む）
FANG_PLUS_SYMBOLS = ['META', 'AMZN', 'AAPL', 'NFLX', 'GOOGL', 'TSLA', 'NVDA', 'MSFT', 'X', 'BABA']

@app.get("/data/")
async def get_data(symbols: str, start: str, end: str):
    try:
        logger.info(f"Fetching data for {symbols} from {start} to {end}")

        # 為替データの取得（ドル円）
        fx_data = yf.download('JPY=X', start=start, end=end)
        fx_data = fx_data.reset_index()
        fx_data['Date'] = fx_data['Date'].dt.strftime('%Y-%m-%d')

        # 複数シンボルのデータ取得
        symbol_list = symbols.split(',')
        all_data = []

        for symbol in symbol_list:
            try:
                stock_data = yf.download(symbol, start=start, end=end)
                if stock_data.empty:
                    logger.warning(f"No data found for symbol: {symbol}")
                    continue
                stock_data = stock_data.reset_index()
                stock_data['Date'] = stock_data['Date'].dt.strftime('%Y-%m-%d')
                stock_data['Symbol'] = symbol
                merged_data = pd.merge(stock_data, fx_data, on='Date', suffixes=('_stock', '_fx'))
                all_data.extend(merged_data.to_dict(orient='records'))
            except Exception as e:
                logger.error(f"Error fetching data for symbol {symbol}: {e}")

        if not all_data:
            raise ValueError("No valid data found for any symbols")

        # データフレームに変換して合計を計算
        df = pd.DataFrame(all_data)
        aggregated_data = df.groupby('Date').sum().reset_index()
        aggregated_data['Date'] = aggregated_data['Date'].astype(str)

        # 規格化
        target_value = 14029.12
        specified_date = '2025-02-14'
        if specified_date in aggregated_data['Date'].values:
            base_value_usd = aggregated_data.loc[aggregated_data['Date'] == specified_date, 'Close_stock'].values[0]
        else:
            aggregated_data['Date'] = pd.to_datetime(aggregated_data['Date'])
            closest_date = aggregated_data.iloc[(aggregated_data['Date'] - pd.to_datetime(specified_date)).abs().argsort()[:1]]['Date'].values[0]
            closest_date = np.datetime_as_string(closest_date, unit='D')
            base_value_usd = aggregated_data.loc[aggregated_data['Date'] == closest_date, 'Close_stock'].values[0]

        aggregated_data['Normalized_Close_stock'] = aggregated_data['Close_stock'] / base_value_usd * target_value

        # 規格化されたドル建て株価から円建てに変換
        aggregated_data['Normalized_Close_JPY'] = aggregated_data['Normalized_Close_stock'] * aggregated_data['Close_fx']

        # ドル円データを追加
        aggregated_data['FX_Close'] = df.groupby('Date')['Close_fx'].first().reset_index(drop=True)

        return aggregated_data.to_dict(orient='records')
    except Exception as e:
        logger.error(f"Error fetching data: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))
```

```index.js
import { useState, useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import { Chart, registerables } from 'chart.js';
import 'chartjs-adapter-date-fns';

Chart.register(...registerables);

export default function Home() {
  const [data, setData] = useState(null);
  const [symbols, setSymbols] = useState('META,AMZN,AAPL,NFLX,GOOGL,TSLA,NVDA,MSFT,X,BABA');
  const [start, setStart] = useState('');
  const [end, setEnd] = useState('');

  useEffect(() => {
    const today = new Date();
    const priorDate = new Date(new Date().setMonth(today.getMonth() - 1));
    setStart(priorDate.toISOString().split('T')[0]);
    setEnd(today.toISOString().split('T')[0]);
  }, []);

  const fetchData = async () => {
    try {
      const response = await fetch(`http://localhost:8000/data?symbols=${symbols}&start=${start}&end=${end}`);
      const result = await response.json();

      // データのコンソール出力
      console.log('Fetched data:', result);

      if (Array.isArray(result) && result.length > 0) {
        setData(result);
      } else {
        console.error('Unexpected data format or no data:', result);
        setData([]);
      }
    } catch (error) {
      console.error('Error fetching data:', error);
      setData([]);
    }
  };

  const chartData = {
    labels: data ? data.map(item => item.Date) : [],
    datasets: [
      {
        label: `Normalized Aggregated Stock Price in USD`,
        data: data ? data.map(item => item.Normalized_Close_stock) : [],
        fill: false,
        backgroundColor: 'rgba(75,192,192,0.4)',
        borderColor: 'rgba(75,192,192,1)',
        yAxisID: 'y-axis-1',
      },
      {
        label: `Normalized Aggregated Stock Price in JPY`,
        data: data ? data.map(item => item.Normalized_Close_JPY) : [],
        fill: false,
        backgroundColor: 'rgba(153,102,255,0.4)',
        borderColor: 'rgba(153,102,255,1)',
        yAxisID: 'y-axis-2',
      },
      {
        label: `USD/JPY Exchange Rate`,
        data: data ? data.map(item => item.FX_Close) : [],
        fill: false,
        backgroundColor: 'rgba(255,99,132,0.4)',
        borderColor: 'rgba(255,99,132,1)',
        yAxisID: 'y-axis-3',
      },
    ],
  };

  const options = {
    scales: {
      x: {
        type: 'time',
        time: {
          unit: 'day',
          tooltipFormat: 'yyyy-MM-dd',
          displayFormats: {
            day: 'yyyy-MM-dd'
          }
        },
        title: {
          display: true,
          text: 'Date'
        }
      },
      'y-axis-1': {
        type: 'linear',
        position: 'left',
        ticks: {
          beginAtZero: false,
        },
      },
      'y-axis-2': {
        type: 'linear',
        position: 'left',
        ticks: {
          beginAtZero: false,
        },
      },
      'y-axis-3': {
        type: 'linear',
        position: 'right',
        grid: {
          drawOnChartArea: false,
        },
        ticks: {
          beginAtZero: false,
        },
      },
    },
  };

  return (
    <div>
      <h1>Normalized Aggregated Stock Price in USD, JPY and USD/JPY Exchange Rate</h1>
      <input placeholder="Symbols (comma-separated)" value={symbols} onChange={(e) => setSymbols(e.target.value)} />
      <input type="date" value={start} onChange={(e) => setStart(e.target.value)} />
      <input type="date" value={end} onChange={(e) => setEnd(e.target.value)} />
      <button onClick={fetchData}>Fetch Data</button>
      {data && (
        <Line data={chartData} options={options} />
      )}
    </div>
  );
}
```

## プログラムの簡単な解説

### バックエンドの解説（FastAPI）

バックエンドでは、FastAPIを使用して株価データを取得し、加工しています。以下は主要なポイントです：

1. **APIエンドポイントの設定**:
    - `/data/`エンドポイントで、複数のシンボルの株価データを指定された期間内で取得します。

2. **データの取得と加工**:
    - `yfinance`ライブラリを使用して、Yahoo Financeから株価データと為替データ（ドル円）を取得します。
    - 取得したデータを日付でマージし、規格化されたドル建て株価と円建て株価を計算します。

3. **エラーハンドリング**:
    - データ取得や処理の過程でエラーが発生した場合、適切なエラーメッセージをログに記録し、HTTPエラーを返します。

### フロントエンドの解説（Next.js）

フロントエンドでは、Next.jsとChart.jsを使用して、取得したデータをグラフで表示します。以下は主要なポイントです：

1. **データの取得**:
    - フロントエンドからバックエンドのAPIエンドポイントにリクエストを送り、株価データを取得します。

2. **データの表示**:
    - Chart.jsを使用して、取得したデータを時間軸のグラフで表示します。
    - 複数の縦軸を設定し、ドル建て株価、円建て株価、およびドル円の為替レートをそれぞれ異なる縦軸で表示します。

3. **UIの構成**:
    - ユーザーがシンボル、開始日、終了日を入力できるインプットフィールドと、データを取得するためのボタンを提供します。

## まとめ

この記事では、FANG+企業のドル建て、円建て価格を取得し、グラフで表示するプログラムを開発しました。プログラムの開発に際しては、データ取得の信頼性、データの整形と前処理、エラーハンドリング、為替レートの取得、グラフの表示に注意を払う必要があります。

具体的には、FastAPIを使用してバックエンドを構築し、Yahoo Financeから株価データと為替データを取得しました。フロントエンドでは、Next.jsとChart.jsを使用して、取得したデータをグラフで表示しました。

このプログラムを通じて、FANG+企業の株価動向をより詳細に把握することができるようになります。この記事が、同様のプロジェクトを進める際の参考になれば幸いです。

最後にイメージを張っておきます。
以下は、FANG+企業のドル建てと円建ての株価、ドル円の為替レートを表示するグラフのイメージです。
このグラフでは、異なる縦軸を使ってドル建て株価、円建て株価、およびドル円の為替レートを表示しています。横軸は日付を示しており、指定された期間内のデータを視覚的に比較できます。
このイメージを使って、記事の視覚的な要素を補完してください。

![スクリーンショット_15-2-2025_1092_localhost.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/64ed132c-3f5d-4a3d-b4ce-5dd34d63716a.jpeg)

最後に、このプログラムはFANG+専用ですが、以下を変更すれば他のETFたとえばTOP20とかに応用できると思います。

```
target_value = 14029.12
specified_date = '2025-02-14'
```