ChatGPTを使用したアプリ開発記【移動平均＋シグナル表示付きチャートを実装】

## ① calculateSMA 関数（単純移動平均）
```AppleStock.js
	//data: 株価の時系列データの配列（例：[{ datetime: '12:00', close: 192.0 }, ...]）。
	//period: 移動平均の期間（デフォルトは5）。5本分の終値を平均してSMAを出す。
function calculateSMA(data, period = 5) {

	//p() を使って、各データ（ローソク足）ごとにSMAを計算。
	//item: 現在のデータ（1本のローソク足）。
	//index: 配列内の位置。
	//arr: 全データ（＝dataと同じ）。
  return data.map((item, index, arr) => {

  	//最初の period - 1 件（例：最初の4本）は、過去5本が揃っていないため SMAは計算できな。
	//その場合は sma: null を付け加えてそのまま返す。
    if (index < period - 1) return { ...item, sma: null };
    
    //slice()で現在の位置から過去 period 本分（例：5本）のデータを抽出。
	//reduce()で close の合計（終値の合計）を求める。
    //arr.slice(2, 6) → index=5なら [2,3,4,5]
    //reduce → その4本の close を合計
    const sum = arr.slice(index - period + 1, index + 1).reduce((acc, cur) => acc + cur.close, 0);

    //現在のデータ item に、sma を加えて返す。
	//sum / period → 終値の合計 ÷ 本数（平均）
    return { ...item, sma: sum / period };
  });
}
```

## ② generateSignals 関数（売買シグナル生成）
```AppleStock.js
	//data: calculateSMA 関数で生成された sma（単純移動平均）付きのローソク足データ配列。
function generateSignals(data) {
  
  	//.map() を使って、すべてのローソク足に「シグナル（BUY/SELL）」を追加していく。
	//item: 現在のローソク足。
	//index: 現在の位置。
	//arr: 元の配列（data）。
  return data.map((item, index, arr) => {

  	//最初のデータ（index === 0）や、sma が存在しないデータ（初期データ）は判定不能。
	//そのため signal: null を返す
    if (index === 0 || !item.sma) return { ...item, signal: null };

	//直前のデータ（1つ前のローソク足）を取得。
	//トレンドの変化を見るためには、前後の比較が必要なので前のデータを参照。
    const prev = arr[index - 1];

    //1本前では終値がSMAを下回っていた (prev.close < prev.sma)。
	//今回の終値がSMAを上回った (item.close > item.sma)。
	//これは「ゴールデンクロス」（上抜け） → 買いシグナル。
    if (prev.close < prev.sma && item.close > item.sma) {

    //現在のデータに "BUY" シグナルを追加して返す。
      return { ...item, signal: 'BUY' }; // 上抜けシグナル
    }

    //1本前では終値がSMAを上回っていた (prev.close > prev.sma)。
	//今回の終値がSMAを下回った (item.close < item.sma)。
	//これは「デッドクロス」（下抜け） → 売りシグナル。
    if (prev.close > prev.sma && item.close < item.sma) {

    //現在のデータに "SELL" シグナルを追加して返す。
      return { ...item, signal: 'SELL' }; // 下抜けシグナル
    }

	//どちらの条件にも当てはまらない場合はシグナルなし。
    return { ...item, signal: null };
  });
}
```

## ③ useEffect 内のデータ取得と加工
```AppleStock.js
//APIから返るデータは最新が最初になっているので、時系列順にするために .reverse() で並び替え。
const reversed = [...json.values].reverse();

//時刻と終値だけを取り出して、チャート描画に適した形式に変換。
//元の item.datetime → "2024-05-25 12:34:00" のような文字列。
const parsed = reversed.map(item => ({
 
  //"12:34:00" だけを抽出。チャートの横軸ラベルに使用するため。
  datetime: item.datetime.split(' ')[1],
  
  //close（終値）を 数値型（float）に変換。
  //元は文字列として返されるので、演算に使えるようにする。
  close: parseFloat(item.close),
}));

//parsed に対して、**5本移動平均（SMA）**を計算し、新たに sma プロパティを付加。
const smaData = calculateSMA(parsed, 5);

//smaData に対して、売買シグナル（BUY/SELL） を計算し、新たに signal プロパティを付加。
const signalData = generateSignals(smaData);

//最新の20本分だけを状態に保存。
setData(signalData.slice(-20));
```
この処理によって、リアルタイム株価 → 加工 → 移動平均 → シグナル追加 → 表示用にセットという一連のデータ準備が完了します。

## ④ Recharts：SMA線と売買シグナルの表示
```AppleStock.js
//青線（close）：実際の1分ごとの終値
<Line type="monotone" dataKey="close" stroke="#007aff" dot={false} name="終値" />

//紫線（sma）：その時点での過去5本分の平均値（トレンドを見る）
<Line type="monotone" dataKey="sma" stroke="#8884d8" dot={false} name="SMA(5)" />
```

## ⑤ Recharts：売買シグナルドット表示
```AppleStock.js
//data 配列（チャート用に整形済の株価データ）を .map() でループ。
//各 item は1分間隔の株価情報で、item.signal に BUY か SELL が入っていることがあります。
//index はそのループ回数（Reactでkeyに使う）。
{data.map((item, index) =>
  
//item.signal に BUY または SELL のどちらかがある場合に限り、以下の ReferenceDot を表示。
//null でないとき（つまりシグナルがあるとき）にドットを描画。
  item.signal ? (

  	//Recharts のコンポーネント。チャート上に点（ドット）を表示するのに使う。
    <ReferenceDot
      
      //Reactのリスト描画に必要な key。
	  //同じ時間が重ならない前提なら index でも可。
      key={index}
      
      //横軸（時間軸）に表示する位置。
	  //item.datetime は HH:mm:ss の形式。
      x={item.datetime}
      
      //縦軸の位置。終値（close）の価格にドットを配置。
      y={item.close}
      
      //ドットの半径を5pxに設定。
	  //値を大きくするとドットが目立ちやすくなる。
      r={5}

      //塗りつぶし色：
	  //BUY なら緑。
	  //SELL なら赤。
      fill={item.signal === 'BUY' ? 'green' : 'red'}
      
      //ドットの外枠の色を黒に設定。
      stroke="black"

      //外枠の太さを1pxに設定。
      strokeWidth={1}

      //ドットの上にラベルを表示する設定。
      label={{
        
      //ラベルに表示する文字列（BUY もしくは SELL）。
        value: item.signal,
        
      	//ドットの上にラベルを表示。
        position: 'top',

        //ラベル文字の大きさ。
        fontSize: 10,

        //ラベルの文字色もドットと同じ色で統一（BUYなら緑、SELLなら赤）。
        fill: item.signal === 'BUY' ? 'green' : 'red',
      }}
    />
  ) : null
)}
```

## UI

![スクリーンショット 2025-05-26 7.59.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1058366/e3be4bf6-1be7-4c5c-bc0f-5f2ee2322d6c.png)