2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Rails]D3.js, Techan.js, APIを使って仮想通貨のローソク足チャートを描画する

Last updated at Posted at 2019-07-12

仮想通貨取引所で公開されているAPIを利用することで、様々な情報を取得することが可能です。
以前の記事では現在の価格を取得する方法を紹介しましたが、今回はローソク足チャートを描画する方法を紹介します。

使用するAPI

bitbankのAPIを使用します。
bitbank.cc API ドキュメント

ローソク足の情報を取得する場合は上記のページのCandlestickを利用します。
例えば、2019年のビットコインの日足チャートを取得する場合は以下のurlにリクエストを送ります。
https://public.bitbank.cc/btc_jpy/candlestick/1day/2019
すると、以下のようなレスポンスが返ってきます。

{
  "success":1,
  "data":{ 
    "candlestick":[{
      "type":"1day",
      "ohlcv":[
        ["406990","420900","400160","419653","1461.5678",1546300800000],
        ["419600","425005","412002","417738","2749.5887",1546387200000],
        ・・・
      ]
    }],
    "timestamp":1562930808433
  }
}

ohlcvに入っている配列がローソク足チャートに必要な情報です。
[始値, 高値, 安値, 終値, 出来高, UnixTime]

環境

バージョン

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18]
$ rails -v
Rails 5.2.3

導入gem

gem 'bootstrap', '~> 4.3.1'
gem 'jquery-rails'
gem 'haml-rails'

今回はjQueryを使用してAjax通信をします。また、レイアウトを整えるためにBootstrapを入れています。ビューにはhamlを採用しています。

ライブラリの読み込み

ローソク足チャートを描画するためにjavascriptのライブラリであるD3.jsと、D3.jsのファイナンス用ライブラリであるTechan.jsをCDNで読み込みます。

headタグの中に以下を追記します。

application.html.haml
  %script{src: "https://d3js.org/d3.v4.min.js", type: "text/javascript"}
  %script{src: "http://techanjs.org/techan.min.js", type: "text/javascript"}

ルーティング

以前の記事と同様にクロスドメイン制約を回避するためにコントローラー経由でAPIサーバーにリクエストを送ります。
なので、今回は名前空間を使って以下のようなルーティングにします。

routes.rb
Rails.application.routes.draw do
  root 'money#show'
  resources :money, only: :show
  namespace :api do
    resources :money, only: :show, defaults: { format: 'json' }
  end
end

ビュー

moneyコントローラーのshowアクションにチャートを表示します。
日足・5分足・1分足チャートを表示するため以下のようなビューにし、card-bodyにチャートを挿入します。

views/money/show.html.haml
# candlestick-1day-container.container.mt-3
  .row
    .col-12.px-0
      %h5.text-right <単位:日足>
      .card
        .card-body.candlestick-1day

# candlestick-5min-container.container.mt-3
  .row
    .col-12.px-0
      %h5.text-right <単位:5分足>
      .card
        .card-body.candlestick-5min

# candlestick-1min-container.container.mt-3
  .row
    .col-12.px-0
      %h5.text-right <単位:1分足>
      .card
        .card-body.candlestick-1min

scssでチャートのフォントサイズと色を調整します。

candlestick.scss
svg text {
  font-size: 14px;
}
path.candle {
  stroke: #000000;
}
path.candle.body {
  stroke-width: 0;
}
path.candle.up {
  fill: #00AA00;
  stroke: #00AA00;
}
path.candle.down {
  fill: #FF0000;
  stroke: #FF0000;
}
path.volume {
  fill: #696969;
  stroke-width: 1;
}
.sma path.line {
  fill: none;
  stroke-width: 1;
}
.ma25 path.line {
  stroke: #0000cd;
}

コントローラー

controllersフォルダ直下のコントローラーとcontrollers/apiフォルダのコントローラーは以下のようになります。

controllers/money_controller.rb
class MoneyController < ApplicationController
  def show
  end
end

今回はcontrollersフォルダ直下のコントローラーでは何もしません。適宜処理を追加してください。

controllers/api/money_controller.rb
class Api::MoneyController < ApplicationController
  # 今回の場合 params[:id] => "btc"
  # jsファイルからリクエストする時にparams[:candleType]に1day、1min、5minを入れる
  def show
    # 日足と分足ではリクエストするurlが変わるので条件分岐してurlをつくる
    if params[:candleType] == "1day"
      # 例) ymd => "2019"
      ymd = Date.today.strftime("%Y")
    elsif params[:candleType] == "1min" || params[:candleType] == "5min"
      # 例) ymd => "20190712"
      ymd = Date.today.strftime("%Y%m%d")
    end
    url = URI.parse("https://public.bitbank.cc/#{params[:id]}_jpy/candlestick/#{params[:candleType]}/#{ymd}")
    response_text = Net::HTTP.get(url)
    response_hash = JSON.parse(response_text)
    render json: response_hash
  end
end

jsファイル

application.js
//= require rails-ujs
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree .

bootstrapがレスポンシブ対応しているので、それに合わせてリサイズした時にもチャートを描画するようにします。

candlestick.js
$(function() {

  //unixTimeをymd形式に変換する関数
  function unixTime2ymd(intTime){
    let d = new Date( intTime );
    let year  = d.getFullYear();
    let month = d.getMonth() + 1;
    let day  = d.getDate();
    let hour = ( '0' + d.getHours() ).slice(-2);
    let min  = ( '0' + d.getMinutes() ).slice(-2);
    return( year + '-' + month + '-' + day + " " + hour + ":" + min);
  };

  // ローソクチャートを表示する関数
  // candletypeには 1min 5min 1dayが選べる
  function displayCandlestick(data, candleType) {

    // グラフを挿入するエリア(card)のwidthを取得,(今回、heightはwidthの8/5に設定)
    let cardWidth = $(`.candlestick-${candleType}`).width();
    let cardHeight = cardWidth / 8 * 5;

    // グラフの領域とマージンを設定
    let margin = {top: 5, right: 5, bottom: 30, left: 80};
    let width = cardWidth - margin.left - margin.right;
    let height = cardHeight - margin.top - margin.bottom;

    // %Y-%m-%d %H:%M の形式のデータを受け取りパースするための変数
    let parseDate = d3.timeParse("%Y-%m-%d %H:%M");

    // グラフ領域のうちx軸が占めるwidthを設定(今回は0~width)
    let x = techan.scale.financetime()
            .range([0, width]);

    // グラフ領域のうちy軸が占めるheightを設定
    // レートと出来高を7対3の比率に設定
    let y = d3.scaleLinear()
            .range([height * 7 / 10, 0]);
    let yVolume = d3.scaleLinear()
            .range([height, height * 7 / 10]);

    // ローソク足チャートをcandlestickとして定義
    let candlestick = techan.plot.candlestick()
            .xScale(x)
            .yScale(y);

    // 日足チャートの場合x軸の定義
    if (candleType == "1day") {
      var xAxis = d3.axisBottom()
            .scale(x)
            .tickFormat(d3.timeFormat("%b")) // 日足なので、月(略称)表示にする
            .ticks(width/90); // 何データずつメモリ表示するか(レスポンシブ対応するためwidthによって変わるようにする)
    // 分足チャートの場合x軸の定義
    } else {
      var xAxis = d3.axisBottom()
            .scale(x)
            .tickFormat(d3.timeFormat("%H:%M")) // 分足なので、時:分表示にする
            .ticks(width/90); // 何データずつメモリ表示するか
    }

    // y軸(レート)の定義
    let yAxis = d3.axisLeft()
            .scale(y)
            .ticks(height/70); // 何データずつメモリ表示するか

    // 移動平均線をsmaとして定義(単純移動平均線 SMA: Simple Moving Average)
    let sma = techan.plot.sma()
            .xScale(x)
            .yScale(y);

    // y軸(出来高)の定義
    let volume = techan.plot.volume()
            .xScale(x)
            .yScale(yVolume);

    // svgの挿入(既存のチャートを削除してから挿入)
    $(`.candlestick-${candleType}`).children("svg").remove();
    let svg = d3.select(`.candlestick-${candleType}`)
            .append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // 日時で並べ替えを行う
    let cAccessor = candlestick.accessor();
    let vAccessor = volume.accessor();
    // 最大180個のデータ数を表示する
    if (data.length > 180) {
      var first_data = data.length - 180;
    } else {
      var first_data = 0;
    }
    data = data.slice(first_data, data.length).map(function(d) {
      return {
          date: parseDate(d.Date),
          open: +d.Open,
          high: +d.High,
          low: +d.Low,
          close: +d.Close,
          volume: +d.Volume
      };
    }).sort(function(a, b) { return d3.ascending(cAccessor.d(a), cAccessor.d(b)); });
    // 描画関数
    x.domain(data.map(cAccessor.d));
    y.domain(techan.scale.plot.ohlc(data, cAccessor).domain());
    yVolume.domain(techan.scale.plot.volume(data, vAccessor.v).domain());

    // 出来高を挿入する
    svg.append("g")
            .attr("class", "volume")
            .data([data])
            .call(volume);
    // ローソク足を挿入する
    svg.append("g")
            .attr("class", "candlestick")
            .data([data])
            .call(candlestick);
    // 移動平均線(データ数25)を追加する
    svg.append("g")
            .attr("class", "sma ma25")
            .datum(techan.indicator.sma().period(25)(data))
            .call(sma);
    // x軸を追加する
    svg.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis);
    // y軸(出来高)を追加する
    svg.append("g")
            .call(d3.axisLeft(yVolume)
            .ticks(height/150) // 何データずつメモリ表示するか
            .tickFormat(d3.format(",.3s")));
    // y軸(レート)を追加する
    svg.append("g")
            .attr("class", "y axis")
            .call(yAxis);
    // y軸のラベルを追加する
    svg.append("g")
            .append("text")
            .attr("transform", "rotate(-90)") // Y軸ラベルを縦書きに
            .attr("y", 15) // 位置調整
            .style("text-anchor", "end") // テキスト開始位置
            .text("価格 (円)");
  }

  // APIから情報を取得し、内部でdisplayCandlestickを呼び出す関数
  // candleTypeには 1min 5min 1dayが選べる
  function getAPIAndDisplayCandlestick(candleType) {
    // コントローラーにリクエストを送る
    let requestUrl = "/api/money/btc";
    $.ajax({
      url: requestUrl,
      type: 'get',
      dataType: 'json',
      data: { candleType: candleType }
    })
    .done(function(json) {
      // ローソク足チャートに必要な情報を取り出す
      let row_data = json.data.candlestick[0].ohlcv
      // 配列dataに連想配列としてデータを入れる
      let data = [];
      row_data.forEach(function(datum) {
        let open = datum[0];
        let high = datum[1];
        let low = datum[2];
        let close = datum[3];
        let volume = datum[4];
        let date = unixTime2ymd(datum[5])
        let modifiedDatum = { Date: date, Open: open, High: high, Low: low, Close: close, Volume: volume };
        data.push(modifiedDatum);
      });
      // 画面を読み込んだ時に発火する
      displayCandlestick(data, candleType);
      // 画面をリサイズした時に発火する
      $(window).on("resize", function() {
        displayCandlestick(data, candleType);
      });
    })
    .fail(function() {
      alert('error');
    });
  }

  // 日足チャートを描画する
  getAPIAndDisplayCandlestick("1day");
  // 5分足チャートを描画する
  getAPIAndDisplayCandlestick("5min");
  // 1分足チャートを描画する
  getAPIAndDisplayCandlestick("1min");
});

これで、以下のように三種類のローソク足チャートが表示されるはずです。
スクリーンショット 2019-07-12 21.33.55.png
スクリーンショット 2019-07-12 21.34.09.png
スクリーンショット 2019-07-12 21.34.23.png

最後に

今回は仮想通貨のローソク足チャートを描画してみました。
techan.jsのサンプルを見ると、ドラッグでチャートを動かしたりズームしたりする機能も追加できるようです。D3.jsとtechan.jsはいろいろできて便利ですね。
ただし、自分自身がD3.jsとtechan.jsについて理解していない部分があり、参考サイトからそのままコピーしたところも多々あります。
もしかしたらおかしな部分があるかもしれません。間違っているところがあればご指摘お願いします。

参考

bitbank.cc API
d3.js
TechanJS Candlestick
【Python】D3.js でWEBページにローソク足チャートを描画する

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?