前回は、取引所のプライベートAPIを用いて自分の取引残高や出金履歴を見てみました。今回は、パブリックAPIを用いて板情報を取得し、Webブラウザ上に表示してみましょう。WebPackとsuperagentを用いたCORS(クロスオリジン)の問題も解決しているのでそれなりに意味のあるものになっているかと思います。今回はReactというライブラリを採用していますが、エッセンスは共通しているはずです。必要なところだけ吸収して頂くだけでも大丈夫です!
Reactの基礎についてはこちらの講座でサクッと学べます(Reactだけでも十分です)。→「最短で学ぶReactとReduxの基礎から実践まで」
1. ワークスペースの作成
今回は雛形(テンプレート)を用意しましたので、こちら(https://github.com/rascal-3/cryptocurrency-api-test.git)のリポジトリにあるソースコードをコピーアンドペーストするか、Cloneしてワークスペースを作ってみてください。もしもご自身のコンピューターにNode.jsが動作する環境があり、Atom等のエディタをお使いになる場合はそちらでも構いませんが、今回もCloud9のサービスを利用したいと思います。(完成版はこちらです→ https://github.com/rascal-3/cryptocurrency-api-react-app)
GitHub上のリポジトリのURL(https://github.com/rascal-3/cryptocurrency-api-test.git)を貼り付け、クローンしてワークスペースを作成します。
今回はパッケージマネージャーとして、「yarn」というものを利用したいと思います。インストールするライブラリ群はpackage.jsonに記述済みですので、まずyarn自体をインストールし、それからyarnでJavaScriptのライブラリをインストールします。(npmでも大丈夫です!)
「npm install -g yarn」を実行します。
完了しましたら、「yarn install」を実行し、ライブラリをインストールします。今回は使わないものも含まれていて少し時間が掛かるかも知れませんがしばらくお待ちください。
インストールが完了すると、自動的にyarn.lockというファイルが生成されますが、これは依存関係を固定させるものですので特に気にしなくても大丈夫です。
2. Webアプリサーバーを起動してみる
次に、package.jsonのscriptsのの箇所をご覧ください。
1 2 3 4 5 |
"scripts": { "dev-start": "./node_modules/.bin/webpack-dev-server", "main": "server.js", "start": "webpack && node server.js" }, |
dev-start, main, startの3つのコマンドがあると思いますが、シェルで「npm run start」を実行してください。
(ローカル環境を構築されている方は、「npm run dev-start」を実行すると、ブラウザで http://localhost:8080/ にアクセスすると、ファイルを編集する度に自動的に変更が反映されるので開発時には便利かと思います。ただし、後でハマることになるCORSのところの対応はしていません。)
↑のような表示がされたら準備は完了です。
3. 各リソースの説明
沢山のファイルがあって驚いたかも知れませんが、見る必要があるものは限られています。今回は触らないものがほとんどですが、まずは簡単にそれぞれのファイルの紹介をしますね。
.eslintrc.js: ESLintという静的解析ツールで、JavaScriptの正しい書き方をしているかを検証する際のルールを記述する設定ファイルです。今回はAirbnbのスタイルを踏襲します(が、Cloud9ではこれが有効にならないのか、”,”のスタイルが適用されませんでした)。
.gitignore: Gitというバージョンコントロールシステムで管理対象外にするファイル等をしていするための設定ファイルです。
server.js: 今回のNode.jsサーバーを起動するためのファイルです。
webpack.config.js: 今回はES2015(ES6)というJavaScriptの書き方を採用しています。クラスの定義やアローファンクションなど新しい機能を使ってすっきりした記述が可能です。その他、SCSSのコンパイル、公開フォルダの指定などを行なっています。また、今回利用している表示(View)を担当するライブラリであるReactのJSXファイルはそのままではブラウザで読み込めませんが、babelというツールを使って通常のJavaScriptに変換しています。このファイルはこれらの一連の動作の定義を行っている設定ファイルです。
public: 公開用ファイルを配置するフォルダです。ここがルートディレクトリとなり、ブラウザから最初に読み込まれるのがpublic/index.htmlとなります。
src: この下にJavaScriptファイルやJSXファイルを作っていきます。適宜フォルダを切って整理しながら作成しましょう。
stylesheets: 初めは気にしなくても構いませんが、ここにHTMLの表示スタイルを記述していきます。HTMLファイルやJSXファイルに直接スタイルを記述することも出来ますが、通常はスタイルシート(CSSやSCSSファイル)として切り出して作成します。Webページの作成に慣れてきたらこちらにスタイルを記述して見た目を美しくして行くと良いと思います。
4. ファイルの作成
今回は、srcフォルダの下にファイルを作成して行きます。
Reactで開発を行なう際は、画面の部品(コンポーネント)毎に1ファイルを作成して行くことが多いようです。ここでは、componentsフォルダの下にアプリのメインコンポーネントであるApp.jsx、bitFlyer用のメインコンポーネントであるBitFlyer.jsx、coincheck用のメインコンポーネントであるCoincheck.jsxが既に用意済みです。
componentsの中にbitFlyer用コンポーネント、coinchek用コンポーネントを作成するためにまずはそれぞれフォルダを作成しましょう。
Cloud9のIDE(統合開発環境)で「File」→「New Folder」を選択し、「bitFlyer」と「coincheck」を作成してください。
次に、↓のようにbitFlyerフォルダの中にBoard.jsxファイルを作成します。
その中に、以下のように記述します。
1 2 3 4 5 6 7 8 9 10 11 |
import React from 'react'; const Board = () => { return ( <div> Hello Crypto-world </div> ); }; export default Board; |
ES2015やReactが初めての方はこの書き方にちょっととまどうかも知れませんね。
まず、import文は、Node.jsのrequire文のように必要なモジュール等を読み込むためのものです。既にnode_modulesの中にreactモジュールがインストールされていますので、単にモジュール名である’react’と指定して読み込みます。
そして、constは定数を定義する際に使用する宣言用のものです。
「() => {}」はアローファンクション(アロー関数)と呼ばれるもので、「function() {}」と同じ意味です。
そして、return文の中にHTMLタグが書かれていて驚いたかも知れませんが、ReactではこのようにJSXファイルをコンポーネントとして記述して行きます。ここでは単純に「Hello Crypto-world」と表示するだけのdivタグを返します。
最後に、export default 変数名 で、他のファイルからそれを呼べるようにします。ここでは、板情報のコンポーネントとして、「Board」を返しています。
では次に、BitFlyer.jsxを見てみましょう。
1 2 3 4 5 6 7 8 9 |
import React from 'react'; const BitFlyer = () => ( <div className="about"> <h1 className="app-title">BitFlyer</h1> </div> ); export default BitFlyer; |
今は↑のようになっているかと思いますので、先ほど作成したBoardを読み込んで表示出来るようにしましょう。
次のように、import文を追加し、BitFlyerコンポーネントの中のh1タグの下に板情報が表示されるようにしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 |
import React from 'react'; import Board from './bitflyer/Board'; const BitFlyer = () => ( <div className="about"> <h1 className="app-title">BitFlyer</h1> <Board /> </div> ); export default BitFlyer; |
import文で、’react’とは違って、’./bitflyer/Board’となっていることにお気づきかと思います。インストールしたモジュールと違って、自身で作成したものについてはパスを指定してあげる必要があるため、このようにBitFlyer.jsxからの相対パスで指定します。
そして、1つだけのコンポーネントなので<Board />のように、閉じタグなしで追記すればOKです。
Cloud9をお使いの方は、一旦コマンドをCtrl+Cで止めて、再度「npm run start」を実行してください(webpack-dev-serverをお使いの方は自動的に反映されます)。そして、画面をリロードすると↓のように「Hello Crypto-world」と表示されればOKです。
5. 板情報の取得機能の実装
では、ここからが本番です。実際に板情報を取得して表示出来るようにしましょう!
板情報の取得は、プライベートAPIを使った前回よりも簡単です。
https://lightning.bitflyer.jp/docs/playgroundを見てみると、次のように書かれていますので、これを1つの関数にしてBoardコンポーネントから呼ぶようにしましょう。
せっかくなのでrequestモジュールの代わりに、今後利用するaxiosというモジュールを使うことにしましょう。(と言いつつ後で違うのを入れることに…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import React from 'react'; import axios from 'axios'; const getBoard = () => { var path = '/v1/getboard'; var query = ''; var url = 'https://api.bitflyer.jp' + path + query; axios .get(url) .then((results) => { console.log(results); }); }; const Board = () => { getBoard(); return ( <div> Hello Crypto-world </div> ); }; export default Board; |
むむっ。エラーが起きました!
「クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://api.bitflyer.jp/v1/getboard にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。」
どうやら違うドメインのデータを利用するところで怒られているようですね…
ハマること数時間…
こうします。一旦、localhostの/api/bf/boardに対してGETリクエストを投げ、そちらで改めてhttps://api.bitflyer.jp/v1/boardに情報を取得するリクエストを投げ、返ってきた値を元のリクエストにレスポンスとして返してあげましょう。
ということで、使うライブラリをaxiosからsuperagentというものに変更してやってみます。
とりあえず、情報が取れることを確認したいので、一旦次のようにbitflyer/Board.jsxを修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import React, { Component } from 'react'; import request from 'superagent'; class Board extends Component { constructor(props) { super(props); this.state = { mid_price: 0, bids: [], asks: [], res: '' }; } componentDidMount() { request.get('/api/bf/board') .end((err, res) => { if (err) { console.log(err); } else if (res.ok) { console.log('res2: ', res); this.setState( { res: res.text }); } }); } render() { return ( <div> Hello {this.state.res} </div> ); } } export default Board; |
superagentの使い方を詳しく知りたい方は検索してみてください。
ここで、Boardがclassになっていることに気付いたでしょうか?
Reactのコンポーネントには、関数型コンポーネントとクラスベースコンポーネントがあり、クラスベースコンポーネントは状態を持つことが出来ます。今回は板の情報を保持したいのでクラスベースに変更しました。
その際、これもES2015の書き方なのですが、ReactのComponentというモジュールを継承する際に、Javaのように「extends」という識別子を使用します。
そして上記のようにコンストラクタを記述し、親クラスであるComponentのコンストラクタを呼び出して初期化します。super(props)としていますね。
ここでpropsとは呼び出し元から設定できるproperties(プロパティ)を表しています。
そして、this.state = {}で、自身の状態変数を定義します。板情報で得られるものは、現在値(mid_price)と買値の配列(bids)、売値の配列(asks)なのでそれぞれの型で予め定義しておきます。resはとりあえずの表示のために用意したstring型の変数です。
初期化の際に呼び出されるコンストラクタ以外でstateに値をセットする場合には、代入記号は使えず、必ずthis.state.setState()を使うことに気を付けてください。
では、ここでserver.jsを編集しましょう。
19 20 21 22 23 24 25 26 27 28 29 30 |
app.get('/api/bf/board', (req, res, next) => { request.get('https://api.bitflyer.jp/v1/board') .end((error, response) => { if (error) { console.log(error); } else if (response.ok) { console.log('GET board OK!!'); res.send(response.body); next(); } }); }); |
app.get()のところでまず、localhostの/api/bf/boardに対してGETが送られてきた際の処理を記述していますね。そしてその中で、superagentのget()ファンクションでbitFlyerのサーバーにリクエストを投げ、返ってきたレスポンスのbodyを元のBoard.jsxのget()がファンクションに返してあげています。
では、一旦プロセスを再起動して画面をリロードしてみましょう。
おぉ!大漁ですね!!
ちょっと疲れてきましたね… でもここまで来たら、データをそれなりに詰めて表示するところまでやりましょう。
とりあえず、bidとaskのそれぞれは「¥400,000: 5.0」のように表示するとするとこれも1つのコンポーネントに出来そうですね。ということで、BidAsk.jsxを作成します。
1 2 3 4 5 6 7 8 9 |
import React from 'react'; const BidAsk = ({ price, size }) => { return ( <div>¥{price.toLocaleString()}: {size}</div> ); } export default BidAsk; |
「¥」は通常のHTMLでの円マークの書き方ですね。
そして、引数のまとまりとしてpropsをもらっています。このコンポーネントを呼び出すときに、引数として、{props.price}のように書くと渡された引数を利用出来るのですが、ここでは、priceとsizeを使うということが分かっているので、
{ price, size }として予め取り出しています。こうすることで、props.を付けなくても使うことが出来ます。
では、これを元のBoard.jsxでインポートして使ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
import React, { Component } from 'react'; import request from 'superagent'; import BidAsk from './BidAsk'; class Board extends Component { constructor(props) { super(props); this.state = { mid_price: 0, bids: [], asks: [], }; } componentDidMount() { request.get('/api/bf/board') .end((err, res) => { if (err) { console.log(err); } else if (res.ok) { // console.log('res: ', res); const body = res.body; this.setState({ mid_price: body.mid_price }); this.setState({ bids: body.bids }); this.setState({ asks: body.asks }); } }); } render() { return ( <div> <table> <thead> <tr><th>bids</th><th>mid-price</th><th>asks</th></tr> </thead> <tbody> <tr><td> </td><td>{ this.state.mid_price}</td><td> </td></tr> <tr> <td> { this.state.bids.map((bid) => { return <BidAsk key={bid.price} price={bid.price} size={bid.size} />; }) } </td> <td> </td> <td> { this.state.asks.map((ask) => { return <BidAsk key={ask.price} price={ask.price} size={ask.size} />; }) } </td> </tr> </tbody> </table> </div> ); } } export default Board; |
今回は適当にTableを使って表示してみましたが、もっとちゃんと表示したい方は是非美しくしてください。ここで、ES2015のmap()を使って、配列それぞれの値を取り出してその各アイテムの値をBidAskコンポーネントに設定しています。keyは実際には使いませんが、コンポーネントで一意になるように価格を設定しています。
出ましたね!見た目は良くないけれど…
ということで、coincheckについても同じようにして、ご自身でやってみてください!もう一つ出来ることとして、一定時間ごとに値を更新することが考えられますね。やってみてください!!
そして、似たようなコンポーネントが出来るな、と思ったらそれを共通化してみることも考えてみましょう!(それを継承して使うように)
次回は、Monacaというプラットフォームでモバイルアプリを作ってみようかと思ったり思わなかったりしています。今回利用したデータを利用して、↓のようなマーケットデプスのグラフなんかも作れたりします。
今回はこんな感じです。お疲れ様でした!!
ということで、今回はbitFlyerのパブリックAPIを用いて板情報の取得を試してみました。
色んな応用が考えられますね!ちょっとReactとES2015の書き方が難しかったでしょうか?でも大丈夫です。覚えやすいと思います!
以下の書籍や、Udemyの講座も参考になるかも知れません。
こちらはPythonを用いたシステムトレードの書籍です。こちらではZaifも取り扱っているようですね。
Rubyで作る! ビットコイン自動売買システム
もう一つ、こちらは動画で学べるコースです。プログラミング言語としてはRubyを用いています。3時間程度で、Rubyの基礎から実際の売買までを行なうところまで学べます。ゆったりしているので、倍速でもご覧いただけるかと思います。学生さんで、プログラミングの講師もなさってるんですね。実は私も会計学を学び始めたばかりの学生でもあります。
お役に立てればとても嬉しいです!何かご要望等がありましたらコメント欄に頂けたらと思います!
今後も色々な技術を採用しつつ少しずつ発展させて行けたらいいなと考えています。よろしくお願いいたします。
Leave your comment.