今日の株価は上がったか？！

**株式投資、やってますか？**
最近は新NISAも始まって、株式投資している人が増えてますよね。

自分の買った銘柄の値段は、毎日気になってチェックしてしまうという人も多いと思います。

今日は、株価の変動をビジュアル化するグッズを作ってみました。

## デイトレーダーとまではいかない
株価というのは、おおむね業績に連動して上がったり下がったりします。
ところが、決算発表で増収増益、コンセンサスも超えているという銘柄が、翌日暴落したりします。全く意味が分かりません。

しかし、1年ぐらいで考えると、利益が出れば株価は上がるはずなので、そういう風に暴落した銘柄は、いずれ元に戻ってさらに高くなることが期待できます。

なので、そんな銘柄に目をつけておいて、大きく下がったら買いを入れる、ということをたまにやります。でも、他にやることもあるし、翌日の朝にスマホをチェックするのを忘れて、大きく下がったことに気が付かない、ということがあって、チャンスを逃しがちです。

## アナログメーターで、株価の動きを示せばよいのでは
そこで、大きく変動したときに、ひと目見て分かるボードが欲しいなと思いました。

そして、ありあわせの材料で作ってみました。

### 材料
- ボードのベースになるもの。iPadのスクリーンフィルムの包装箱を利用しました。
- 割り箸
- obniz board Y
- サーボモーター
- クリップ

以上す。サーボモータはベースに軽くネジ止めしています。

こんな感じに出来ました。

![IMG_0223.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212141/e3eb8651-c49d-4c5d-43e6-95bc2f42dc08.jpeg)

## どうやって使うの
指定した一銘柄の現在の株価が、前日の終値よりも上がったら右方向のプラス側に割り箸が倒れます。逆に下がったら左方向のマイナス側に倒れます。変わっていなければ、垂直方向を向きます。
右に振り切ったら20％以上の上昇、左に振り切ったら20％以上の下降、というように設定しました。サーボモーターは0から180度まで動きますので、プラマイ90度で上昇、下降を示すことができます。

銘柄の指定は、Lineから株価コードを入れることで指定します。例えば、トヨタ自動車であれば、7203.T という風に入れると指定できます。

株価コード以外の文字列をLineに入れると、怒られます。

日本の株式市場は、午前9時に始まって、11時半に昼休みに入ります。午後は12時半からで、午後3時になると終了します。この間、株価は細かく変動してきます。
なので、この市場が空いている間は、5秒おきに新しい株価を取得して、このメーターに反映させます。市場が終わると、明日まで完全に停止します。

## 動かしてみました

<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">株価が上がったら、右の青に倒れます。<br>下がったら、左の赤に倒れます。<br>やばいっ、大暴落じゃん！<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/%E6%A0%AA%E5%BC%8F%E6%8A%95%E8%B3%87?src=hash&amp;ref_src=twsrc%5Etfw">#株式投資</a> <a href="https://twitter.com/hashtag/obniz?src=hash&amp;ref_src=twsrc%5Etfw">#obniz</a> <a href="https://twitter.com/hashtag/make?src=hash&amp;ref_src=twsrc%5Etfw">#make</a> <a href="https://twitter.com/hashtag/JavaScript?src=hash&amp;ref_src=twsrc%5Etfw">#JavaScript</a> <a href="https://t.co/OLrAqCJRDJ">pic.twitter.com/OLrAqCJRDJ</a></p>&mdash; Tadashi Okuno (@tadokuno) <a href="https://twitter.com/tadokuno/status/1805484129838145544?ref_src=twsrc%5Etfw">June 25, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">前日終値比較、右がプラスで左がマイナス。<br>今日の午前中のトヨタ自動車の株価の動きを、タイムラプスで撮ってみた。<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/%E6%A0%AA%E4%BE%A1?src=hash&amp;ref_src=twsrc%5Etfw">#株価</a> <a href="https://twitter.com/hashtag/obniz?src=hash&amp;ref_src=twsrc%5Etfw">#obniz</a> <a href="https://twitter.com/hashtag/%E3%82%BF%E3%82%A4%E3%83%A0%E3%83%A9%E3%83%97%E3%82%B9?src=hash&amp;ref_src=twsrc%5Etfw">#タイムラプス</a> <a href="https://t.co/MlfaUcLyuN">pic.twitter.com/MlfaUcLyuN</a></p>&mdash; Tadashi Okuno (@tadokuno) <a href="https://twitter.com/tadokuno/status/1805485955518022134?ref_src=twsrc%5Etfw">June 25, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 制作過程
ちょっとした工作をしたので、楽しいですね。仕上げにこだわらなかったので、見た目は？ですが。

箱にちょうどサーボモーターが入る穴を開けて、そこにモーターを固定します。
固定するネジがついていたので、ボードの段ボール板にネジ止めしてみると、割としっかりマウントされました。
割り箸は、モーターに取り付ける羽に両面テープで貼り付けて、さらにセロテープでぐるぐる巻きました。

箱の中に、obniz board Yをセロテープで動かないように貼り付けてあります。

外部から電源供給しますので、ケーブルが通る穴を箱に開けました。

![20240625_062735752_iOS.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212141/4279fe45-993b-14f0-262b-5047aa839583.jpeg)



### 株価の取得
最初は、RapidAPIという色々なサイトのデータを入手できるサービスを経由して、Yahoo Financeから株価を取るようにしました。これは、ChatGPTの回答だったのですが、残念ながらこれは使えなかったです。
というもの、一ヶ月のリクエストが50件リミットという、とんでもない制限が掛かっていたからです。

株価はフリーで取得できるサイトがいっぱいあったと思ったので、Google検索で普通に探したところ、直でYahooにアクセスすれば無料でデータが取れることが分かりました。マジか。
このサイトは、株価の履歴や企業の業績まで入手できる非常にスグレモノのサイトです。
この記事を参考にしました。

https://tech-garage-blog.com/api/977/

### 実行環境

実行環境は、ローカルで簡易Webサーバーを立てます。そこに、htmlとJavaScriptのソースを置いて、PCのブラウザ上で実行することにしました。

そこでエラーが発生。
> Access to fetch at 'https://query1.finance.yahoo.com/v8/finance/chart/7203.T' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

どうも、localhostから、このクエリを呼び出すことができないようです。Node.jsなら問題なく動くのですが、ブラウザの制限だと思われます。

解決策として、ChatGPTが示したのは、Proxy Serverを立てて、それを経由する方法です。なんかもっとうまい方法がありそうですが、それほど難しくないので、言われた通りにしました。


1.Node.jsとExpressのインストール
```bash
mkdir proxy-server
cd proxy-server
npm init -y
npm install express
```
2.サーバーのコーディング (server.js)
```javascript
const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(cors());

app.get('/stock/:code', async (req, res) => {
    const stockCode = req.params.code;
    const API_URL = `https://query1.finance.yahoo.com/v8/finance/chart/${stockCode}`;

    try {
        const response = await fetch(API_URL);
        const data = await response.json();
        res.json(data);
    } catch (error) {
        res.status(500).json({ error: 'Error fetching data' });
    }
});

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});
```


3.起動
```bash
node server.js
```

このserver.jsの中で、Yahoo financeのサイトを指定します。
そして、実際の株価取得のロジックでは、localhostを指定するようにします。

### Lineの入力から株価を取得し、obnizでサーボモーターを動かす
Lineの入力は、株価コードとしてGoogle Sheetに反映させました。そのロジックはmakeを使って開発しました。

Webアプリ側 (JavaScript) では、定期的にGoogle Sheetの株価コードを取得して、その株価をネットから検索し、サーボモーターを制御するプログラムを実行します。

全体の構成図は、以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212141/a391a3fe-6914-ee6f-210c-f673d12d577c.png)


### Lineの入力をGoogle Sheetに反映させる。

これには、makeを使いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212141/31826a34-58e5-02ac-eb3a-5d88c9330038.png)

株価コードとパターンが一致する場合は、Google Sheetの指定したセルに書き込む、それ以外は、GPTにリクエストして会社名を株価コードに変換して、それをセルに書き込む。それが上手く出来ない場合はエラーを返すという、３通りの結果が得られるシンプルなロジックになりました。

### 株価コードの読み込みとobnizの制御

Googleシートから5秒ごとに株価コードを入手して、そのコードの株価情報をYahoo Financeから取得し、現在の株価と前日終値とを比較して、obnizの角度を決めます。

サーボモータは、反時計回りがプラスの角度として作られています。要するに右ネジということになりますが、軸を手前に持ってきた場合には、一般的なプラスは時計回りになります。
なので、株価と角度は比例はするが、向きは逆ということになります。そのあたりを考慮して計算しました。


#### obnizとサーボモータの接続
obnizのio0,1,2にそれぞれサーボモーターのグランド、電源、信号線をつなぎます。
> io0 servomotor gnd
> io1 servomotor vcc
> io2 servomotor signal

下の写真のような感じです。実際はサーボモーターもメスコネクタなので、間にパッチを入れて繋ぎました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212141/ac350d7a-86c1-d5ea-70d5-97a3e2100e47.png)


完成したコードは以下の通り(index.html, yfinance.js)

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Stock Price</title>
  <script src="https://unpkg.com/obniz@3.30.0/obniz.js"></script>
  <script src="yfinance.js"></script>
</head>
<body>
  <div id="obniz-debug"></div>
  <h1>株価チェック</h1>
  <p id="currentPrice">現在の株価: 取得中...</p>
  <p id="previousClosePrice">前日の終値: 取得中...</p>
  <p id="parameter">パラメーター: 計算中...</p>

  <script>
    const obniz = new Obniz('Obniz_ID'); // Obniz_IDに自分のIDを入れます

    // obnizがオンラインであることが確認されたら、以下の関数内が自動で実行されます
    obniz.onconnect = async function () {
      const servo = obniz.wired("ServoMotor", {gnd:0, vcc:1, signal:2});

      setInterval(async() => {
        let value = 0;
        const res = await fetch('https://api.steinhq.com/v1/storages/667629714d11fd04f00b339a/シート1?search={"device":"StockCode"}', { 
          method: "GET"
        })
        .then(response => response.json())
        .then(data => {
          value = data[0].value;
          console.log(data[0].value)
        })
        .catch(err => {
          console.log(err)
        })
        const stockCode = value;
        getStockPrice(stockCode).then(prices => {
            if (prices) {
                document.getElementById('currentPrice').textContent = `現在の株価: ${prices.currentPrice}`;
                document.getElementById('previousClosePrice').textContent = `前日の終値: ${prices.previousClosePrice}`;
                document.getElementById('parameter').textContent = `パラメーター: ${prices.parameter}`;

                console.log(prices.currentPrice);
	        console.log(prices.previousClosePrice);
        	servo.angle(prices.parameter);
            }
        });
      }, 5000) // 5000 m秒（5秒）ごとに距離を測る。5000の値を変えると間隔を変えられるが、あまり短すぎるとセンサーのラグに阻まれる
    }
  </script>
</body>
</html>
```
```javascript
// 株価を取得する関数
async function getStockPrice(stockCode) {
   // const API_URL = `https://query1.finance.yahoo.com/v8/finance/chart/${stockCode}`;
    const API_URL = `http://localhost:3000/stock/${stockCode}`;

    try {
        const response = await fetch(API_URL);
        const data = await response.json();

        // データが正しく取得できたか確認
        if (!data.chart || !data.chart.result || !data.chart.result[0]) {
            throw new Error('データを取得できませんでした');
        }

        const result = data.chart.result[0];
        const meta = result.meta;

        // 現在の株価と前日の終値を取得
        const currentPrice = meta.regularMarketPrice;
        const previousClosePrice = meta.previousClose;

        console.log(`現在の株価: ${currentPrice}`);
        console.log(`前日の終値: ${previousClosePrice}`);

        // パラメーターを計算
        let parameter;
        const lowerBound = previousClosePrice * 0.8;
        const upperBound = previousClosePrice * 1.2;

        if (currentPrice === previousClosePrice) {
            parameter = 90;
        } else if (currentPrice <= lowerBound) {
            parameter = 180;
        } else if (currentPrice >= upperBound) {
            parameter = 0;
        } else {
            // 現在の株価が前日の終値の±20%の範囲内にある場合の計算
            const range = upperBound - lowerBound;
            parameter = 180 - ((currentPrice - lowerBound) / range) * 180;
        }

        console.log(`パラメーター: ${parameter.toFixed(2)}`);

        // 必要に応じて、これらの値をDOMに表示するなどの処理を追加
        return { currentPrice, previousClosePrice, parameter: parameter.toFixed(2) };
    } catch (error) {
        console.error(error);
    }
}
```
Webアプリの画面はこんな感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212141/c291ebd9-b46b-810c-e911-3e5531e5f039.png)


## やってみて
Lineの入力をトリガーにして動かすというところで、直接JavaScriptのコードを呼び出すことを最初考えて、全てをmakeで作りたいと思ったのですが、obnizのライブラリとか、他にも外部ライブラリを参照するなら実行環境の準備が大変かも、というのがあり、一旦は暗礁に乗り上げました。

ただ、よく考えてみるとLineの入力はキューに入れるだけにして、別のプログラムからそのキューを読み出すというような考え方で実装したほうが簡単だということに気付き、その線で完成させました。一番悩んだのはこのあたりです。

ただ、株価の更新サイクルと、株価コードの取得サイクルを同じにしたので、Line入力から最大5秒という長いインターバルが発生するのは改善のポイントだと思われます。トリガは別にした方がよかったです。

## 今後の拡張予定
~~Lineから入れるのを株価コードではなく、適当な会社名やインデックスの名称でも指定できるようにします。
入力は曖昧なものを許すようにしたいので、生成AIを使うつもりです。~~

1. 設定して、変更されましたという応答をLineがするのだが、この応答が株価コードになっています。
例えば、三菱、と入力したときに、三菱商事なのか、三菱電機なのか、三菱UFJ銀行なのか、が確認出来ないので、そこも会社名を返したくなりました。

1. 株価がある一定値上がる、もしくは下がると音がと光が出るようにします。今はモーターの「ジジ」という音しかしないので、気付きにくく、もともとの目的に合ってないような気もするので。

以上