KabuステーションAPIをNode.jsで利用するまでの準備

auカブコム証券ではAPIを使った売買ができるらしいので、一獲千金を夢見て(無理) Node.js からkabuステーションAPIが利用できるように準備してみる。

#kabuステーションの用意
まずはauカブコム証券で口座を作り、kabuステーションという有料のツールを申し込む必要がある。有料と言っても、普通に取引をしていれば無料で使えるし、月額利用料は1,000円程度なので勉強代と思って良いレベルかと思う。

kabuステーションを入手したら、[kabuステーションAPI](https://kabucom.github.io/kabusapi/ptal/index.html)のポータルサイトから、[kabuステーション®APIを利用するための初期設定](https://kabucom.github.io/kabusapi/ptal/howto.html)を参考に、APIを有効化する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935134/7c5fc825-ee7a-a5aa-0be9-ffcc3d7b6e28.png)
「本番用」と「検証用」の2種類のパスワードは、実際にAPIを利用するときに必要になるのでそれぞれ設定して控えておく。

#kabuステーションAPIの種類
kabuステーションAPIにはREST APIとWebsocketの2種類が用意されており、それぞれ以下のような機能が提供されている。

* [REST API](https://kabucom.github.io/kabusapi/reference/index.html): 認証、口座や銘柄の情報の取得や、Websocketで配信する銘柄の登録が行える。
* [Websocket](https://kabucom.github.io/kabusapi/ptal/push.html): REST APIで登録した銘柄の情報が取得できる。(受信専用)

これらのAPIにはそれぞれ「本番用」と「検証用」があり、ベースURLは以下のようになっている。「検証用」では実データのやり取りはできず、応答はあってもNullや空データしか返ってこないので、売買の動作確認はかなり勇気がいる作業になりそう。

| API種類 | 用途 | ベースURL |
|:-:|:-:|:--|
| REST API | 本番用 | http://localhost:18080/kabusapi |
| REST API | 検証用 | http://localhost:18081/kabusapi |
| Websocket | 本番用 | ws://localhost:18080/kabusapi/websocket |
| Websocket | 検証用 | ws://localhost:18081/kabusapi/websocket |

ここで`localhost`を見て「あれ?」と思うかもしれないが、これはkabuステーションAPIはインストールしたkabuステーションがAPIを公開していることによる。そのため、kabuステーションを起動し、ログインしていないと使えないことに注意する。

#基本的なREST APIの使い方
REST APIを使い始める前に[1]トークンを取得する。これは1回だけ行えばよく、複数回取得したら過去のトークンは無効になる。データは[APIのリファレンス](https://kabucom.github.io/kabusapi/reference/index.html#tag/auth%2Fpaths%2F~1token%2Fpost)に載っている形式なので、応答データからトークン(`Token`)だけ取り出して以降のリクエスト発行時に使用する。

トークンが取得できたら、[2]ヘッダの`X-API-KEY`に取得したトークンを載せてリクエストを発行する。以下の例では「取引余力(現物)」を取得している。取得データは`{ StockAccountWallet: number }`の形式([APIのリファレンス](https://kabucom.github.io/kabusapi/reference/index.html#tag/wallet%2Fpaths%2F~1wallet~1cash%2Fget)では`null`と書かれているが、`number`が正しい)。実行してみると口座の残高が表示される。

リクエストに失敗したら例外が発生するので、[3]必要なら例外をCatchする。`catch`の引数`err`が`AxiosError`(HTTPクライアントAxiosのエラー)であれば、`err.response.data`を参照することで何がまずかったか調べることができるようになっている。例えば`X-API-KEY`を付け忘れると`{ Code: 4001009, Message: 'APIキー不一致' }`というエラーコードとメッセージになる。どんなエラーコード・エラーメッセージがあるかは、[APIのリファレンス](https://kabucom.github.io/kabusapi/reference/index.html)の「レスポンス」の項に載っている。

```typescript
import axios from 'axios';

const restBase = 'http://localhost:18080/kabusapi'; // こちらは本番用
const apiPassword = '本番用のパスワード';

const api = axios.create( {
  baseURL: restBase
} );

async function sample() {
  try {
    // [1] APIトークンを取得する
    const tokenResult = await api.post( '/token', { APIpassword: apiPassword } );
    const token = tokenResult.data.Token; // トークンを得る

    // [2] 任意のリクエストを発行する。以下は取引余力(現物)を取得する例。
    const wallet = await api.get( '/wallet/cash', {
      headers: {
        'X-API-KEY': token
      }
    } );
    const stockAccountWallet = wallet.data.StockAccountWallet; // 余力 = 口座の残高を得る
    console.log( stockAccountWallet );
  }catch(err) {
    // [3] isAxiosErrorが真であれば、応答データを参照できる。
    if( err.isAxiosError ) {
      console.log( err.response.data );
    }
  }
}

// サンプル実行
sample();
```

# OpenAPIで自動コード生成して使う
限られたAPIを利用するだけなら`Axios`でべた書きしても良いが、せっかくOpenAPIの仕様(YAMLファイル)が提供されているので、コードを自動生成することでAPIを使いやすくすることができる。

## コード生成
コード生成には`openapi-generator-cli`を使う。以下はグローバルインストールの場合のコマンドを示しているが、インストールはローカル・グローバルどちらでも良い。

```
npm install -g @openapitools/openapi-generator-cli
```

コード生成の入力となるOpenAPIの仕様は、[kabuステーションAPIのリポジトリ](https://github.com/kabucom/kabusapi)内の`references/kabu_STATION_API.yaml`にある。リポジトリをどこかにクローンしたら、コードを生成したいフォルダ内で以下ようなコマンドを実行する。

```
openapi-generator-cli generate -g typescript-axios -i path/to/kabu_STATION_API.yaml --skip-validate-spec
```
`-i`オプションの`path/to/kabu_STATION_API.yaml`にはOpenAPI仕様へのパスを指定する。

`-g`オプションはGenerator Nameで、生成するコードの言語やHTTPクライアントごとに何種類かある。それぞれAPIをどのようなクラス・メソッドで実現するか異なるので、実際に作ってみて使い勝手を比べた方が良い。利用可能なGeneratorは、[OpenAPI GeneratorのGenerators](https://github.com/OpenAPITools/openapi-generator/tree/master/docs/generators)の一覧を参照のこと。(`typescript-node`というGeneratorは最近のNodeでは動かないみたいなので、ここではAxiosを選択した)

`--skip-validate-spec`オプションが無いとエラーでコードが生成されないことに注意する。これはkabuステーションAPIの一部のパスに@が含まれており、検証でエラーになるのを回避するために必要となる。検証しなくてもエラー個所はAPIの仕様通りのコードが生成されるため、特に気にしなくても問題なかった。

コマンドを実行すると、実行したフォルダ内に以下のようなファイルが生成される。(以下は`typescript-axios`の結果。使用したGeneratorによって異なる)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935134/3f251251-9a9a-5b4f-faa1-02cf4448ea7c.png)
ここでは、生成したコード(`.ts`ファイル)をプロジェクトの`src/kabusapi`にコピーして使用する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/935134/e6e7b9a2-60f4-3624-35c0-186cae3fc456.png)

## 生成したコードからKabuステーションAPIを使う
`Axios`を使ってべた書きしたのと同じことを、OpenAPIで生成したコードを使って書き直すと以下のようになる。

エンドポイントごとにクラスになっているので、1つ1つインスタンス化して使うのは少しまどろっこしく感じるが、引数にパラメータを与えれば適切にREST APIに渡してくれるので便利になったと思う。

```ts
// OpenAPIから生成したコード
import { AuthApi, WalletApi } from './kabusapi';

const restBase = 'http://localhost:18080/kabusapi'; // こちらは本番用
const apiPassword = '本番用のパスワード';

// エンドポイントごとのクラスをインスタンス化
const auth = new AuthApi( { basePath: restBase } );
const wallet = new WalletApi( { basePath: restBase } );

async function sample() {
  try {
    // [1] APIトークンを取得する
    const tokenResult = await auth.tokenPost( { APIPassword: apiPassword } );
    const token = tokenResult.data.Token;
    
    // [2] 任意のリクエストを発行する。以下は取引余力(現物)を取得する例。
    const walletResult = await wallet.walletCashGet( token );
    const stockAccountWallet = walletResult.data.StockAccountWallet;
    console.log( stockAccountWallet );
  }catch(err) {
    if( err.isAxiosError ) {
      console.log( err.response.data );
    }
  }
}

// サンプル実行
sample();
```
> 余談だが、`AuthApi`や`WalletApi`のインスタンス化の際、特に指定しなければ`GlobalAxios`というAxiosのグローバルインスタンスを使用してリクエストを発行するようになる。グローバルインスタンスを使いたくなければ、コンストラクタの第3引数で個別にAxiosインスタンスを指定することもできる。

# Push API(Websocket)を使う
株価や板のリアルタイム情報が欲しい場合、Push APIを使う。(板情報といっても、売買それぞれ10ずつしか見えない制限がある)

Push APIを使うには、まずREST APIで情報を配信したい銘柄を登録する。これには[1]トークンを取得し、[2]銘柄登録を行う。

銘柄登録できたら情報の配信が始まるので、Push API(Websocket)に接続する。[3]でWebsocketに接続し、[4]接続後はメッセージを受け取るたびに必要な処理を行う。(以下の例では終了処理を省略している)

どんな情報が得られるかは、[Push APIのリファレンス](https://kabucom.github.io/kabusapi/ptal/push.html)を参照のこと。

```ts
import { AuthApi, RegisterApi } from './kabusapi';
import { client as WebSocketClient } from 'websocket';

const restBase = 'http://localhost:18080/kabusapi'; // こちらは本番用
const apiPassword = '本番用のパスワード';

// REST API
const auth = new AuthApi( { basePath: restBase } );
const register = new RegisterApi( { basePath: restBase } );

// Push API
const wsUrl = 'ws://localhost:18080/kabusapi/websocket'; // こちらは本番用
const ws = new WebSocketClient();

async function sample() {
  try {
    // [1] APIトークンを取得する
    const tokenResult = await auth.tokenPost( { APIPassword: apiPassword } );
    const token = tokenResult.data.Token;
    
    // [2] 銘柄登録
    // Qiitaの運営会社「エイチーム」を購読対象に追加する
    const registerResult = await register.registerPut( token, { Symbols: [ { Symbol: '3662', Exchange: 1 } ] } );
    const list = registerResult.data.RegistList;
    console.log( list );

    connect(); // Websocketに接続
  } catch(err) {
    if( err.isAxiosError ) {
      console.log( err.response.data );
    }
  }
}

function connect() {
  // [3] KabuステーションのWebsocketに接続する。
  ws.connect( wsUrl );
  ws.on( 'connect', ( connection ) => {
    console.log( 'connected.' );

    // [4] メッセージを受け取るごとに処理を行う。
    connection.on('message', ( msg ) => {
      if( msg.type === 'utf8' ) {
          const data = JSON.parse( msg.utf8Data );
          console.log( data );
      }
    } );

    connection.on( 'close', () => {
      console.log('closed.');
    } );
  } );
}

// サンプル実行
sample();
```

このサンプルを実行すると、以下のようなデータが更新があるたびに得られる。(具体的な数字は伏せてある)

```
{
  OverSellQty: xxxx,
  UnderBuyQty: xxxx,
  TotalMarketValue: xxx,
  MarketOrderSellQty: xxxx,
  MarketOrderBuyQty: xxxx,
  BidTime: '2020-xx-xxTxx:xx:xx+09:00',
  AskTime: '2020-xx-xxTxx:xx:xx+09:00',
  Exchange: 1,
  ExchangeName: '東証１部',
  TradingVolume: xxxx,
  TradingVolumeTime: '2020-xx-xxTxx:xx:xx+09:00',
  VWAP: 1089.4837,
  TradingValue: xxxx,
  BidQty: xxxx,
  BidPrice: xxxx,
  BidSign: '0101',
  Sell1: {
    Price: xxxx,
    Qty: xxxx,
    Sign: '0101',
    Time: '2020-xx-xxTxx:xx:xx+09:00',
  },
  Sell2: { Price: xxxx, Qty: xxxx },
  Sell3: { Price: xxxx, Qty: xxxx },
  Sell4: { Price: xxxx, Qty: xxxx },
  Sell5: { Price: xxxx, Qty: xxxx },
  Sell6: { Price: xxxx, Qty: xxxx },
  Sell7: { Price: xxxx, Qty: xxxx },
  Sell8: { Price: xxxx, Qty: xxxx },
  Sell9: { Price: xxxx, Qty: xxxx },
  Sell10: { Price: xxxx, Qty: xxxx },
  AskQty: xxxx,
  AskPrice: xxxx,
  AskSign: '0101',
  Buy1: {
    Price: xxxx,
    Qty: xxxx,
    Sign: '0101',
    Time: '2020-xx-xxTxx:xx:xx+09:00',
  },
  Buy2: { Price: xxxx, Qty: xxxx },
  Buy3: { Price: xxxx, Qty: xxxx },
  Buy4: { Price: xxxx, Qty: xxxx },
  Buy5: { Price: xxxx, Qty: xxxx },
  Buy6: { Price: xxxx, Qty: xxxx },
  Buy7: { Price: xxxx, Qty: xxxx },
  Buy8: { Price: xxxx, Qty: xxxx },
  Buy9: { Price: xxxx, Qty: xxxx },
  Buy10: { Price: xxxx, Qty: xxxx },
  Symbol: '3662',
  SymbolName: 'エイチーム',
  CurrentPrice: xxxx,
  CurrentPriceTime: '2020-xx-xxTxx:xx:xx+09:00',
  CurrentPriceChangeStatus: '0058',
  CurrentPriceStatus: 1,
  CalcPrice: xxxx,
  PreviousClose: xxxx,
  PreviousCloseTime: '2020-xx-xxTxx:xx:xx+09:00',
  ChangePreviousClose: xxxx,
  ChangePreviousClosePer: xxxx,
  OpeningPrice: xxxx,
  OpeningPriceTime: '2020-xx-xxTxx:xx:xx+09:00',
  HighPrice: xxxx,
  HighPriceTime: '2020-xx-xxTxx:xx:xx+09:00',
  LowPrice: xxxx,
  LowPriceTime: '2020-xx-xxTxx:xx:xx+09:00',
}
```

# まとめ
KabuステーションAPIをNode.jsで使うまでの準備をまとめた。まずはKabuステーションに申し込み、APIを有効化すれば、KabuステーションAPIが使えるようになる。REST APIをそのままHTTPクライアントから叩くのもありだが、OpenAPI仕様を使ってコード生成すれば手軽に利用できるようになる。Push APIはREST APIで銘柄登録し、Websocketで接続することで、株価や板のリアルタイム情報が得られるようになる。