ESP32でYahoo! finance APIを叩いて株価・為替情報を取得するには

# はじめに

ESP32を使って株価情報や為替情報を表示させてみたいと思いました。
その際、APIをいろいろ調査しました。それらの情報をこちらにコミットしておきます。

# Yahoo! finance APIで為替情報を取得するには
次のAPIにアクセスしてください。

```
https://query1.finance.yahoo.com/v8/finance/chart/USDJPY=X?interval=1d
```

[リンクはこちら](https://query1.finance.yahoo.com/v8/finance/chart/USDJPY=X?interval=1d)

対円通貨コード一覧表(ex.USDJPY=X)は[こちら](https://support.yahoo-net.jp/PccFinance/s/article/H000006607)

# 東証の株価を取得するには
次のAPIにアクセスしてください。

```
https://query1.finance.yahoo.com/v8/finance/chart/4442.T?interval=1d
```

[リンクはこちら](https://query1.finance.yahoo.com/v8/finance/chart/4442.T?interval=1d)

XXXX.Tの.Tは東証のことです。

# 日経平均株価を取得するには
次のAPIにアクセスしてください。

```
https://query1.finance.yahoo.com/v8/finance/chart/^N225?interval=1d
```
[リンクはこちら](https://query1.finance.yahoo.com/v8/finance/chart/^N225?interval=1d)

^N225は日経平均の証券コードです。

# サンプルソース

サンプルソースはこちらです。M5Stackで動作確認を行いました。

動作風景はこちら

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">とりあえず、M5Unifiedを使って、表示してみた。<br>Yahoo!financeAPIを叩いて、為替と日経平均株価と、ある企業の株価を表示してみました。<br>次はRCAモジュールを使って、テレビに表示してみます。画面デザイン力はない。。<a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://t.co/g8fSn7yjzC">pic.twitter.com/g8fSn7yjzC</a></p>&mdash; riraosan (@riraosan_0901) <a href="https://twitter.com/riraosan_0901/status/1717844723589890524?ref_src=twsrc%5Etfw">October 27, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

```c++
#include <memory>
#include <Arduino.h>
#include <SD.h>
#include <SPIFFS.h>
#include <M5ModuleRCA.h>
#include <M5Unified.h>

// basic
#include <WiFi.h>
#include <HTTPClient.h>
#include <Ticker.h>

#define HTTP_BUFFER 1024 * 2

// github
#include <ArduinoJson.h>
#include <StreamUtils.h>

// yahoo finance (yahoo.com) Root Certificate
constexpr const char *ca = R"(-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
)";

const char *ssid     = "your ssid";   // Enter SSID
const char *password = "your password";  // Enter Password

// Yahoo! finance API endpoint
String              endpoint("https://query1.finance.yahoo.com/v8/finance/chart/{code}?interval=1d");
DynamicJsonDocument _doc(HTTP_BUFFER);

float _regularMonyOrder  = 0.0;
float _reqularStockPrice = 0.0;

// APIにリクエストを送る→Jsonレスポンス取得
long doHttpGet(String url, const char *ca) {
  log_i("[HTTP] GET begin...\n");
  HTTPClient http;

  http.begin(url, ca);

  log_i("[HTTP] GET...");
  // start connection and send HTTP header
  int           httpCode = http.GET();
  unsigned long index    = 0;

  // httpCode will be negative on error
  if (httpCode > 0) {
    // HTTP header has been send and Server response header has been handled
    log_i("[HTTP] GET... code: %d", httpCode);

    // file found at server
    if (httpCode == HTTP_CODE_OK) {

      ReadLoggingStream    loggingStream(http.getStream(), Serial);
      DeserializationError error = deserializeJson(_doc, loggingStream);

      if (error) {
        log_e("deserializeJson() failed: %s", error.f_str());
        http.end();
        return -1;
      }
    } else {
      http.end();
      log_e("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
      return -1;
    }
  } else {
    http.end();
    log_e("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    return -1;
  }

  http.end();

  return 0;
}

// Mony order code is ex. USDJPY=X. etc...
float getMonyOrder(String monyordercode) {
  String api(endpoint);
  api.replace("{code}", monyordercode);

  if (doHttpGet(api, ca)) {
    log_e("fail to load json data.");
  }

  JsonObject chart_result_0 = _doc["chart"]["result"][0];
  if (chart_result_0.isNull() == false) {
    JsonObject chart_result_0_meta = chart_result_0["meta"];
    if (chart_result_0_meta.isNull() == false) {
      return chart_result_0_meta["regularMarketPrice"];
    }
  }

  return -1;
}

float getStockPrice(String stockcode) {
  String api(endpoint);
  api.replace("{code}", stockcode);

  if (doHttpGet(api, ca)) {
    log_e("fail to load json data.");
  }

  JsonObject chart_result_0 = _doc["chart"]["result"][0];
  if (chart_result_0.isNull() == false) {
    JsonObject chart_result_0_meta = chart_result_0["meta"];
    if (chart_result_0_meta.isNull() == false) {
      return chart_result_0_meta["regularMarketPrice"];
    }
  }

  return -1;
}

void initWifi(void) {
  // Connect to wifi)
  WiFi.begin(ssid, password);

  log_i(" WiFi connecting");

  // Wait some time to connect to wifi
  for (int i = 0; i < 30 && WiFi.status() != WL_CONNECTED; i++) {
    log_i(".");
    delay(1000);
  }

  log_i("\n Connected!");
}

void setup(void) {
  auto cfg = M5.config();

  cfg.external_display.module_rca = true;  // default=true. use ModuleRCA VideoOutput

  // setting for Module RCA.
  cfg.module_rca.logical_width  = 320;
  cfg.module_rca.logical_height = 240;
  cfg.module_rca.output_width   = 320;
  cfg.module_rca.output_height  = 240;
  cfg.module_rca.signal_type    = M5ModuleRCA::signal_type_t::NTSC_J;      //  NTSC / NTSC_J / PAL_M / PAL_N
  cfg.module_rca.use_psram      = M5ModuleRCA::use_psram_t::psram_no_use;  // psram_no_use / psram_half_use
  cfg.module_rca.pin_dac        = GPIO_NUM_26;
  cfg.module_rca.output_level   = 128;

  // begin M5Unified.
  M5.begin(cfg);

  // Get the number of available displays
  int display_count = M5.getDisplayCount();

  for (int i = 0; i < display_count; ++i) {
    int textsize = M5.Displays(i).height() / 60;
    if (textsize == 0) {
      textsize = 1;
    }
    M5.Displays(i).setTextSize(textsize);
    M5.Displays(i).printf("No.%d\n", i);
  }

  // If an external display is to be used as the main display, it can be listed in order of priority.
  M5.setPrimaryDisplayType({m5::board_t::board_M5ModuleRCA});

  // The primary display can be used with M5.Display.
  M5.Display.print("primary display\n");

  M5.delay(5000);

  initWifi();

  float monyorder  = getMonyOrder("USDJPY=X");
  float nikkeiavg  = getStockPrice("^N225");
  float stockprice = getStockPrice("4442.T");

  M5.Displays(0).setCursor(0, 0);
  M5.Displays(0).printf("USD-JPY\n %05.1f[JPY]\n", monyorder);
  M5.Displays(0).printf("NIKKEI AVG\n %05.1f[JPY]\n", nikkeiavg);
  M5.Displays(0).printf("4442.T\n %05.1f[JPY]\n", stockprice);
}

void loop(void) {
  M5.delay(1);
}


```




# さいごに

HttpClientライブラリでリクエストを投げてください。
ArduinoJsonライブラリを使ってレスポンスのJSONをパースして各種数値を取得すれば、ESP32でTickerを作れると思います。
（了）