日本語

MT4とNode.jsを使って遊んでみた

サムネ
おはようございます。 fx-on.comエンジニアの岩淵です。 今日はNode.jsのお話です。 どんな技術かは前から知っていたのですが、やる気が出なくて忙しくてなかなか覚える暇がありませんでした。 最近になってようやくやる気がでてきた暇ができたので、ちょっとサンプルを製作してみました。

作りたいもの

・MT4から取得したレートをリアルタイムで表示するページ

作るもの

・サーバーへレート情報を渡すためのEA(NodeJsTest.mq4) ・Node.jsのサーバー(server.js) ・取得したレートを表示するためのページ(index.html)

製作

まずはEAを作成します。 難しいコードは一切書きません。 ティックが動く毎にWebRequest関数を使ってサーバーにデータを渡すだけです。 渡すデータはブローカー名、通貨ペア名、Bid値の3つです。
#property copyright "Copyright 2016, gogojungle"
#property version   "1.00"
#property strict

string URL = "https://rate-server-keisukeiwabuchi-1.c9users.io/";

int OnInit(){
   return(INIT_SUCCEEDED);
}

void OnTick(){
   string data = "company=" + AccountCompany() + "&symbol=" + Symbol() + "&rate=" + DoubleToString(Bid, Digits);
   POST(URL, data);
}

bool POST(string url, string data){
   string method = "POST";
   string cookie = NULL;
   int timeout = 10000;
   char post[];
   char result[];
   string headers;
   string comment = NULL;
   StringToCharArray(data, post);
   
   ResetLastError();
   uint debug_timeout = GetTickCount();
   int res = WebRequest(method, url, cookie, NULL, timeout, post, ArraySize(post), result, headers);

   if(res == -1){
      Print(__FUNCTION__ + " Error code =",GetLastError());
      if(GetTickCount() > debug_timeout + timeout) Print("WebRequest get timeout");
         
      return(false);
   }
   
   Print("POST success! ", CharArrayToString(result, 0, -1));
   return(true);
}
続いてサーバー側の実装です。 Node.jsのsocket.ioというモジュールを利用します。 socket.ioを使うことでページを閲覧中のユーザー(クライアント)に対してサーバーから情報を送ることができます。 POST形式でMT4からデータが送られてくるので、POSTが来たら閲覧中の全ユーザー(クライアント)に対してレート情報を送信するようにします。 POST以外でアクセスが来た場合(ブラウザからの閲覧)には、このあと作成するindex.htmlの内容を読み込んで表示します。 socket.ioではemitで情報を送信、onで情報を受信できるようです。 io.sockets.emitとすると接続中の全クライアントに対して送信できます。 このあたりは融通が効くので特定のグループにのみ送信なんかもできるようです。
var new_rate = [];
var fs     = require('fs'),
    qs     = require('querystring'),
    http = require('http').createServer(function(req, res){
  if(req.method === 'POST'){
    req.data = '';
    req.on('readable', function(){
      req.data += req.read();
    });
    
    req.on('end', function(){
      var query = qs.parse(req.data);
      
      new_rate[query.company] = [];
      new_rate[query.company][query.symbol] = query.rate;
      
      res.writeHead(200);
      res.write(new_rate[query.company][query.symbol]);
      res.end();
      
      var d = new Date();
      var date = toDoubleDigits(d.getHours()) +":"+toDoubleDigits(d.getMinutes())+":"+toDoubleDigits(d.getSeconds())+"."+d.getMilliseconds();
      
      switch(query.company){
        case 'OANDA DIVISION9':
          io.sockets.emit('time_data_oanda', date);
          io.sockets.emit('rate_data_oanda', new_rate[query.company][query.symbol]);
          break;
        case 'Gaitame Finest Company Limited':
          io.sockets.emit('time_data_gaitame', date);
          io.sockets.emit('rate_data_gaitame', new_rate[query.company][query.symbol]);
          break;
        case 'Ava Financial Ltd.':
          io.sockets.emit('time_data_ava', date);
          io.sockets.emit('rate_data_ava', new_rate[query.company][query.symbol]);
          break;
        case 'Forex Capital Markets, LLC':
          io.sockets.emit('time_data_fxcm', date);
          io.sockets.emit('rate_data_fxcm', new_rate[query.company][query.symbol]);
          break;
        case 'FXTrade Financial Co., Ltd':
          io.sockets.emit('time_data_fxtf', date);
          io.sockets.emit('rate_data_fxtf', new_rate[query.company][query.symbol]);
          break;
        case 'GAIN Capital Japan Co., Ltd.':
          io.sockets.emit('time_data_gain', date);
          io.sockets.emit('rate_data_gain', new_rate[query.company][query.symbol]);
          break;
        default:
          break;
      }
    });
  }
  else{
    fs.readFile(__dirname + '/index.html', function(err, data){
      if(err){
        res.writeHead(500);
        return res.end('Error');
      }
  
      res.writeHead(200);
      res.write(data);
      res.end();
    });
  }
});

var toDoubleDigits = function(num) {
  num += "";
  if (num.length === 1) {
    num = "0" + num;
  }
 return num;     
};

var io = require('socket.io').listen(http);
http.listen(process.env.PORT);
io.set('log level', 1);
続いてレートを閲覧するためのページをhtmlで作成します。 サーバーから受け取った情報を表示するための入れ物を用意しておいてあげます。 scriptタグの中でonでサーバーからデータを受信し、JQueryで表示を変えています。 見栄えをよくするためにBootstrapでスタイルを作成しています。 ファイルを用意するのは面倒なのでJQueryとBootstrapはCDNから読み込みます。
<!doctype html>
<html lang="ja">
    <head>
        <title>Rate Server</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js">
    </head>
    <body>
        <nav class="navbar navbar-default">
            <div class="container">
                <div class="navbar-header">
                    <h1 class="navbar-brand">USDJPY</h1>
                </div>
            </div>
        </nav>
        <div class="container-fluid">
            <div class="row">
                <div class="col-sm-12">
                    <div class="col-sm-2 bg-success">
                        <h2>OANDA</h2>
                        <h3>最終更新: <span id="time_OANDA"></span></h3>
                        <ul id="OANDA"></ul>
                    </div>
                    <div class="col-sm-2 bg-info">
                        <h2>Gaitame</h2>
                        <h3>最終更新: <span id="time_Gaitame"></span></h3>
                        <ul id="Gaitame"></ul>
                    </div>
                    <div class="col-sm-2 bg-warning">
                        <h2>FXTF</h2>
                        <h3>最終更新: <span id="time_FXTF"></span></h3>
                        <ul id="FXTF"></ul>
                    </div>
                    <div class="col-sm-2 bg-success">
                        <h2>Ava</h2>
                        <h3>最終更新: <span id="time_AVA"></span></h3>
                        <ul id="AVA"></ul>
                    </div>
                    <div class="col-sm-2 bg-info">
                        <h2>楽天</h2>
                        <h3>最終更新: <span id="time_FXCM"></span></h3>
                        <ul id="FXCM"></ul>
                    </div>
                    <div class="col-sm-2 bg-warning">
                        <h2>GAIN</h2>
                        <h3>最終更新: <span id="time_GAIN"></span></h3>
                        <ul id="GAIN"></ul>
                    </div>
                </div>
            </div>
        </div>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
        <script src="//socket.io/socket.io.js"></script>
        <script>
            $(function(){
                var socket = io.connect();
                socket.on('time_data_oanda', function(data){
                    $('#time_OANDA').html(data);
                });
                socket.on('time_data_gaitame', function(data){
                    $('#time_Gaitame').html(data);
                });
                socket.on('time_data_fxtf', function(data){
                    $('#time_FXTF').html(data);
                });
                socket.on('time_data_ava', function(data){
                    $('#time_AVA').html(data);
                });
                socket.on('time_data_fxcm', function(data){
                    $('#time_FXCM').html(data);
                });
                socket.on('time_data_gain', function(data){
                    $('#time_GAIN').html(data);
                });
                
                socket.on('rate_data_oanda', function(data){
                    $('#OANDA').prepend($('<li>').text(data));
                });
                socket.on('rate_data_gaitame', function(data){
                    $('#Gaitame').prepend($('<li>').text(data));
                });
                socket.on('rate_data_fxtf', function(data){
                    $('#FXTF').prepend($('<li>').text(data));
                });
                socket.on('rate_data_ava', function(data){
                    $('#AVA').prepend($('<li>').text(data));
                });
                socket.on('rate_data_fxcm', function(data){
                    $('#FXCM').prepend($('<li>').text(data));
                });
                socket.on('rate_data_gain', function(data){
                    $('#GAIN').prepend($('<li>').text(data));
                });
            });
        </script>
    </body>
</html>
ちなみに今回はCloud 9というブラウザ上で動くIDEを使って環境構築しています。 Cloud 9でNode.jsのプロジェクトを作成すると、Node.jsやsocket.ioがインストール済みのサーバーが用意されるので、環境構築の手間がかからずサクッと開発できて便利です。 jsファイル内の「process.env.PORT」という記述はCloud 9のポート番号を取得するための記述なので、普通のサーバーで試すときにはここを適切なポート番号に変更して下さい。 サーバーの準備が完了したらサーバーを起動してEAを設置します。 これでサーバーへとレート情報が送られている状態になります。 ブラウザからサーバーへアクセスすると各社のドル円レートがドンドン追加されていきます。 デモページ ※このデモサイトはそのうち終了すると思います。(計測を維持するのが手間なので・・・) 感想 今回Node.jsを使ってみて感じたことは、Node.jsって投資サイトとの相性がすごくいいですね。 FXではリアルタイムな情報が重要なので、非同期でサクサクと配信できるNode.jsはかなり重宝すると思います。 レートの閲覧以外にも、ユーザー同士でチャートを共有したり、ポジション情報を共有できたりと色々面白いことができそうです。 fx-on.comでしたら、みんなのMT4で他のトレーダーの取引をリアルタイムに閲覧できたり、お問い合わせをチャットで素早く回答できたりと、色々なところに活かせそうです。 Node.jsはJavaScriptで書くので、JavaScriptさえできればすぐに覚えられるのも良いところですね。 ブラウザ側だけでなくMT4側でもサーバーからいつでも情報を受け取れる状態にできるともっと色々できそうですが、MQL4だけでは無理そうですね。(C++で書いてDLLを作ればなんとかなるのかな?)