English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
ソーシャルテクノロジースタートアップの構築 パート2: MQL5 REST クライアントのプログラミング

ソーシャルテクノロジースタートアップの構築 パート2: MQL5 REST クライアントのプログラミング

MetaTrader 5トレーディング | 24 12月 2015, 13:27
701 0
laplacianlab
削除済み

はじめに

本稿パート1では、いわゆるソーシャルディシジョン支援システムのアーキテクチャを提示しました。このシステムの片側は Expert Advisors の自動判断をサーバーに送信する MetaTrader 5 ターミナルで構成されています。通信のもう一方にはこういうトレードシグナルを受信し、MySQL データベースにそれを格納し、最終的に人々にツイートするSlim PHP フレームワーク上に構築された Twitter アプリケーションがあります。の主な目標は自動シグナルに関して行われる人間の行動を記録することと適切に人間の意思決定を行うことです。これが可能なのは、自動シグナルがこのようにエキスパートのひじょうに多くの観客にさらされるためです。

後者の部分でわれわれは MQL5 プログラム言語を使って SDSS のクライアント側を開発しようとしているのです。いくつかの選択肢を検討し、またそれぞれの賛成点反対点を特定します。その後、パズルのピースをすべてつなげ、最終的 にExpert Advisorsからトレードシグナルを受信する PHP REST API を形作ります。 これを達成するためにはクライアント側のプログラミングに関わるいくつかの側面を考慮する必要があります。

これでみなさんは MQL5 のトレードシグナルをツイートできるのです!

これでみなさんは MQL5 のトレードシグナルをツイートできるのです!


1. SDSS のクライアント側

1.1. OnTimer イベント内でのトレードシグナルのツイート

私はシンプル化という問題について OnTimer イベントからのトレードシグナル送信を示しながら考察しましたこのシンプルな例がどのように動作するか見れば、定番の Expert Advisor に対するこのふるまいを推定するのはひじょうに簡単になります。

dummy_ontimer.mq5:

#property copyright     "Author: laplacianlab, CC Attribution-Noncommercial-No Derivate 3.0"
#property link          "https://www.mql5.com/ja/users/laplacianlab"
#property version       "1.00"
#property description   "Simple REST client built on the OnTimer event for learning purposes."

int OnInit()
  {          
   EventSetTimer(10);    
   return(0);  
  }
  
void OnDeinit(const int reason)
  {  
  }
  
void OnTimer()
  {
//--- REST client's HTTP vars
   string uri="http://api.laplacianlab.com/signal/add";
   char post[];
   char result[];
   string headers;
   int res;
   string signal = "id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&";
   StringToCharArray(signal,post);
//--- reset last error
   ResetLastError();
//--- post data to REST API
   res=WebRequest("POST",uri,NULL,NULL,50,post,ArraySize(post),result,headers);
//--- check errors
   if(res==-1)
     {
      Print("Error code =",GetLastError());
      //--- maybe the URL is not added, show message to add it
      MessageBox("Add address '"+uri+"' in Expert Advisors tab of the Options window","Error",MB_ICONINFORMATION);
     }
   else
     {
      //--- successful
      Print("REST client's POST: ",signal);
      Print("Server response: ",CharArrayToString(result,0,-1));
     }         
  }

ご覧のように、このクライアントアプリケーションの中ほどは新しい MQL5 の WebRequest 関数です。

HTTP 通信を処理するカスタム MQL5 コンポーネントをプログラミングすることはこのソリューションの代替ですが、それでもこの新しい言語の性質を使って MetaQuotes にタスクを任せる方が安全です。

上記の MQL5 プログラムは以下を出力します。

OR      0       15:43:45.363    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
KK      0       15:43:45.365    RESTClient (EURUSD,H1)  Server response: {"id_ea":"1","symbol":"AUDUSD","operation":"BUY","value":"0.9281","id":77}
PD      0       15:43:54.579    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
CE      0       15:43:54.579    RESTClient (EURUSD,H1)  Server response: {"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
ME      0       15:44:04.172    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
JD      0       15:44:04.172    RESTClient (EURUSD,H1)  Server response: {"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
NE      0       15:44:14.129    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
ID      0       15:44:14.129    RESTClient (EURUSD,H1)  Server response: {"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
NR      0       15:44:24.175    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
IG      0       15:44:24.175    RESTClient (EURUSD,H1)  Server response: {"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
MR      0       15:44:34.162    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
JG      0       15:44:34.162    RESTClient (EURUSD,H1)  Server response: {"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
PR      0       15:44:44.179    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
CG      0       15:44:44.179    RESTClient (EURUSD,H1)  Server response: {"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
HS      0       15:44:54.787    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
KJ      0       15:44:54.787    RESTClient (EURUSD,H1)  Server response: {"id_ea":"1","symbol":"AUDUSD","operation":"BUY","value":"0.9281","id":78}
DE      0       15:45:04.163    RESTClient (EURUSD,H1)  REST client's POST: id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&
OD      0       15:45:04.163    RESTClient (EURUSD,H1)  Server response: {"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}

サーバーは以下のメッセージによっと応答することに留意ください。

{"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}

これは SDSS をハイパーアクティブなスキャルプロボットから防ぐため API メソッド signal/add 内に実装されているちょっとしたセキュリティメカニズムのためです。

/**
 * REST method.
 * Adds and tweets a new trading signal.
 */
$app->post('/signal/add', function() {
    $tweeterer = new Tweeterer();
    // This condition is a simple mechanism to prevent hyperactive scalpers
    if ($tweeterer->canTweet($tweeterer->getLastSignal(1)->created_at, '1 minute'))
    {
        $signal = (object)($_POST);
        $signal->id = $tweeterer->addSignal(1, $signal);
        $tokens = $tweeterer->getTokens(1);
        $connection = new TwitterOAuth(
            API_KEY, 
            API_SECRET, 
            $tokens->access_token, 
            $tokens->access_token_secret);
        $connection->host = "https://api.twitter.com/1.1/";
        $ea = new EA();
        $message = "{$ea->get($signal->id_ea)->name} on $signal->symbol. $signal->operation at $signal->value";
        $connection->post('statuses/update', array('status' => $message));           
        echo '{"status": "ok", "message": {"text": "Signal processed."}}';
    }   
});

受信しているHTTP リクエストが悪意のあるものではないことをウェブサーバーがすでに確認(たとえば受信シグナルはサービス攻撃をひとつも拒否しない)した直後に、上記のシンプルなメカニズムはウェブアプリ内で作用し始めます。

ウェブサーバーはそのような攻撃を阻止する役目を担っています。例としてApache は回避的モジュールと安全モジュールを組み合わせることでそれらを防ぎます。

これは、サーバーアドミニストレータがアプリが毎秒などに受け入れる HTTP リクエストを制御することのできる典型的な Apache の mod_evasive のコンフィギュレーションです。

<IfModule mod_evasive20.c>
DOSHashTableSize    3097
DOSPageCount        2
DOSSiteCount        50
DOSPageInterval     1
DOSSiteInterval     1
DOSBlockingPeriod   60
DOSEmailNotify someone@somewhere.com
</IfModule>

PHP メソッド canTweet の目標は、SDSSによって HTTP の攻撃とみなされないハイパーアクティブなスキャルパをブロックすることです。canTweet メソッドは Twetterer クラス(これについては後にお話します)内に実装されます。

/**
 * Checks if it's been long enough so that the tweeterer can tweet again
 * @param string $timeLastTweet e.g. 2014-07-05 15:26:49
 * @param string $timeWindow A time window, e.g. 1 hour
 * @return boolean
 */
public function canTweet($timeLastTweet=null, $timeWindow=null)
{
    if(!isset($timeLastTweet)) return true;
    $diff = time() - strtotime($timeLastTweet);
    switch($timeWindow)
    {
        case '1 minute';                
            $diff <= 60 ? $canTweet = false : $canTweet = true;                
            break;                
        case '1 hour';                
            $diff <= 3600 ? $canTweet = false : $canTweet = true;                
            break;                
        case '1 day':                                
            $diff <= 86400 ? $canTweet = false : $canTweet = true;                
            break;
        default:                
            $canTweet = false;                
            break;                
    } 
    if($canTweet)
    {
        return true;
    }
    else 
    {
        throw new Exception('Please wait until the time window has elapsed.');
    }
}

WebRequest がわれわれのために自動で構築してくれる HTTP リクエストヘッダフィールドを見ます。

Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*

WebRequest の POST はプログラマーが HTML 形式のデータを送りたがっていると想定します。ですがこのシナリオではサーバーに以下の HTTP リクエストヘッダフィールドを送信したいと思っています。

Content-Type: application/json
Accept: application/json

解決の確実な方法はないのでわれわれはみずからの決定に一貫性を持ち、賛成点と反対点を発見するためにどれほどわれわれの条件に適しているか徹底的に調査します。

技術的観点からすると、ほんとうに HTTP REST のダイアログを確立することは正しいでしょう。ですが、申し上げたように、WebRequest() がもともとウェブサービスではなくウェブページ向けだったみたいだとしても、HTTP ダイアログを MetaQuotes に委任するのがより安全です。われわれが最終的にクライアントのシグナルを url エンコードするのがこれが理由です。API は url エンコードされたシグナルを受信し、その後それを PHP の stdClass フォーマットに変換します。

WebRequest() 関数を使用することに対する代替は、wininet.dll ライブラリを使用するオペレーションシステムに近いレベルのカスタム MQL5 コンポーネントを書くことです。記事Using WinInet.dll for Data Exchange between Terminals via the Internet および Using WinInet in MQL5. Part 2: POST Requests and Files がこの方法の基本を説明しています。ただし、MQL5 の開発者とMQL5 Community の経験はこのソリューションは一見するほど簡単ではないことを示してきました。MetaTrader が更新されるとき、WinINetに対する呼び出しが切断されるという欠点を提示しています。

1.2. EA のトレードシグナルのツイート

ここで最近説明したことを推定します。私は制御されたスキャルピングとサービス攻撃の拒否に関する問題を説明するために以下のダミーロボットを作成しました。

Dummy.mq5:

//+------------------------------------------------------------------+
//|                                                        Dummy.mq5 |
//|                               Copyright © 2014, Jordi Bassagañas |
//+------------------------------------------------------------------+
#property copyright     "Author: laplacianlab, CC Attribution-Noncommercial-No Derivate 3.0"
#property link          "https://www.mql5.com/ja/users/laplacianlab"
#property version       "1.00"
#property description   "Dummy REST client (for learning purposes)."
//+------------------------------------------------------------------+
//| Trade class                                                      |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Declaration of variables                                         |
//+------------------------------------------------------------------+
CPositionInfo PositionInfo;
CTrade trade;
MqlTick tick;
int stopLoss = 20;
int takeProfit = 20;
double size = 0.1;
//+------------------------------------------------------------------+
//| Tweet trading signal                                             |
//+------------------------------------------------------------------+   
void Tweet(string uri, string signal)
  {
   char post[];
   char result[];
   string headers;
   int res;
   StringToCharArray(signal,post);
//--- reset last error
   ResetLastError();
//--- post data to REST API
   res=WebRequest("POST",uri,NULL,NULL,50,post,ArraySize(post),result,headers);
//--- check errors
   if(res==-1)
     {
      //--- error
      Print("Error code =",GetLastError());
     }
   else
     {
      //--- successful
      Print("REST client's POST: ",signal);
      Print("Server response: ",CharArrayToString(result,0,-1));
     }         
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {                
   return(0);  
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+  
void OnDeinit(const int reason)
  {  
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+  
void OnTick()
  {
//--- update tick
   SymbolInfoTick(_Symbol, tick);
//--- calculate Take Profit and Stop Loss levels
   double tp;
   double sl;   
   sl = tick.ask + stopLoss * _Point;
   tp = tick.bid - takeProfit * _Point;
//--- open position
   trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,size,tick.bid,sl,tp);
//--- trade URL-encoded signal "id_ea=1&symbol=AUDUSD&operation=BUY&value=0.9281&";
   string signal = "id_ea=1&symbol=" + _Symbol + "&operation=SELL&value=" + (string)tick.bid + "&";
   Tweet("http://api.laplacianlab.com/signal/add",signal);
}

上記のコードほど簡単なものはありません。この Expert Advisor はティックごとに1つショートポジションを注文するだけです。このため、特に変動の多い時間帯に実行すると、このロボットは短い時間間隔で多くのポジションを注文してしまいます。心配することはありません。サーバー側が DoS 攻撃を失せぐためにウェブサーバーを構成することと、説明したように PHP アプリケーション内のウィンドウ一定時間を決めることの両方でツイート間隔を制御します。

これを明確にすることで、みなさんはこの EA の Tweet 関数を取り入れ、それをお気に入りのExpert Advisor に入れることができます。

1.3. ユーザーがツイートしたトレードシグナルを見るには?

次の例では、@laplacianlab が SDSS に前のセクションに掲示しているダミー EA のシグナルをツイートする許可を与えています。

図1 @laplacianlab は SDSS に自分の代わりにツイートする許可を出しています。

図1 @laplacianlab は SDSS に自分の代わりにツイートする許可を出しました。

ところで、この例ではボリンジャーバンドの名前が出ています。それはこれが本稿の最初の部分で MySQL データベースに格納したものの一つだからです。id_ea=1 は『ボリンジャーバンド』に関連していましたが、この説明にふさわしいものとなるよう、それを『ダミー』のようなものに変更する必要があります。いずれにせよ、これは二次的な側面ですが、このちょっとした不便をお許しください。

MySQL データベースは最終的には以下のようなものとなります。

# MySQL database creation...

CREATE DATABASE IF NOT EXISTS laplacianlab_com_sdss;

use laplacianlab_com_sdss;

CREATE TABLE IF NOT EXISTS twitterers (
    id mediumint UNSIGNED NOT NULL AUTO_INCREMENT, 
    twitter_id VARCHAR(255),
    access_token TEXT,
    access_token_secret TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS eas (
    id mediumint UNSIGNED NOT NULL AUTO_INCREMENT, 
    name VARCHAR(32),
    description TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),    
    PRIMARY KEY (id)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS signals (
    id int UNSIGNED NOT NULL AUTO_INCREMENT,
    id_ea mediumint UNSIGNED NOT NULL,
    id_twitterer mediumint UNSIGNED NOT NULL,
    symbol VARCHAR(10) NOT NULL,
    operation VARCHAR(6) NOT NULL,
    value DECIMAL(9,5) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id),
    FOREIGN KEY (id_ea) REFERENCES eas(id),
    FOREIGN KEY (id_twitterer) REFERENCES twitterers(id)
) ENGINE=InnoDB;

# Dump some sample data...

# As explained in Part I, there's one single twitterer

INSERT INTO eas(name, description) VALUES
('Bollinger Bands', '<p>Robot based on Bollinger Bands. Works with H4 charts.</p>'),
('Two EMA', '<p>Robot based on the crossing of two MA. Works with H4 charts.</p>');


2. SDSS のサーバー側

われわれのソーシャルディシジョン支援システムのサーバー側の作成を続ける前に、今次のようなディレクトリ構成であることを簡単に思い出します。

図2 Slim ベースの PHP API のディレクトリストラクチャ

図2 Slim ベースの PHP API のディレクトリストラクチャ

2.1. PHP API コード

説明したことに従い、index.php ファイルは以下のように記述されています。

/**
 * Laplacianlab's SDSS - A REST API for tweeting MQL5 trading signals
 *
 * @author      Jordi Bassagañas
 * @copyright   2014 Jordi Bassagañas
 * @link        https://www.mql5.com/ja/users/laplacianlab
 */

/* Bootstrap logic */
require_once 'config/config.php';
set_include_path(get_include_path() . PATH_SEPARATOR . APPLICATION_PATH . '/vendor/');
set_include_path(get_include_path() . PATH_SEPARATOR . APPLICATION_PATH . '/model/');
require_once 'slim/slim/Slim/Slim.php';
require_once 'abraham/twitteroauth/twitteroauth/twitteroauth.php';
require_once 'Tweeterer.php';
require_once 'EA.php';
session_start();

/* Init Slim */
use \Slim\Slim;
Slim::registerAutoloader();
$app = new Slim(array('debug' => false));
$app->response->headers->set('Content-Type', 'application/json');

/**
 * Slim's exception handler
 */
$app->error(function(Exception $e) use ($app) {
    echo '{"status": "error", "message": {"text": "' . $e->getMessage() . '"}}';
});

/**
 * REST method.
 * Custom 404 error.
 */
$app->notFound(function () use ($app) {
    echo '{"status": "error 404", "message": {"text": "Not found."}}';
});

/**
 * REST method.
 * Home page.
 */
$app->get('/', function () {
    echo '{"status": "ok", "message": {"text": "Service available, please check API."}}';
});

/**
 * REST method.
 * Adds and tweets a new trading signal.
 */
$app->post('/signal/add', function() {
    $tweeterer = new Tweeterer();
    // This condition is a simple mechanism to prevent hyperactive scalpers
    if ($tweeterer->canTweet($tweeterer->getLastSignal(1)->created_at, '1 minute'))
    {
        $signal = (object)($_POST);
        $signal->id = $tweeterer->addSignal(1, $signal);
        $tokens = $tweeterer->getTokens(1);
        $connection = new TwitterOAuth(
            API_KEY, 
            API_SECRET, 
            $tokens->access_token, 
            $tokens->access_token_secret);
        $connection->host = "https://api.twitter.com/1.1/";
        $ea = new EA();
        $message = "{$ea->get($signal->id_ea)->name} on $signal->symbol. $signal->operation at $signal->value";
        $connection->post('statuses/update', array('status' => $message));           
        echo '{"status": "ok", "message": {"text": "Signal processed."}}';
    }   
});

/**
 * REST implementation with TwitterOAuth.
 * Gives permissions to Laplacianlab's SDSS to tweet on the user's behalf.
 * Please, visit https://github.com/abraham/twitteroauth
 */
$app->get('/tweet-signals', function() use ($app) {   
    if (empty($_SESSION['twitter']['access_token']) || empty($_SESSION['twitter']['access_token_secret']))
    {
        $connection = new TwitterOAuth(API_KEY, API_SECRET);
        $request_token = $connection->getRequestToken(OAUTH_CALLBACK);
        if ($request_token)
        {
            $_SESSION['twitter'] = array(
                'request_token' => $request_token['oauth_token'],
                'request_token_secret' => $request_token['oauth_token_secret']
            );
            switch ($connection->http_code) 
            {
                case 200:
                    $url = $connection->getAuthorizeURL($request_token['oauth_token']);                    
                    // redirect to Twitter
                    $app->redirect($url);
                    break;
                default:
                    throw new Exception('Connection with Twitter failed.');
                break;
            }
        }
        else 
        {
            throw new Exception('Error Receiving Request Token.');
        }
    } 
    else 
    {    
        echo '{"status": "ok", "message": {"text": "Laplacianlab\'s SDSS can '
        . 'now access your Twitter account on your behalf. Please, if you no '
        . 'longer want this, log in your Twitter account and revoke access."}}';
    }    
});

/**
 * REST implementation with TwitterOAuth.
 * This is the OAuth callback of the method above.
 * Stores the access tokens into the database.
 * Please, visit https://github.com/abraham/twitteroauth
 */
$app->get('/twitter/oauth_callback', function() use ($app) {
    if(isset($_GET['oauth_token']))
    {
        $connection = new TwitterOAuth(
            API_KEY, 
            API_SECRET, 
            $_SESSION['twitter']['request_token'], 
            $_SESSION['twitter']['request_token_secret']);
        $access_token = $connection->getAccessToken($_REQUEST['oauth_verifier']);
        if($access_token)
        {       
            $connection = new TwitterOAuth(
                API_KEY, 
                API_SECRET, 
                $access_token['oauth_token'], 
                $access_token['oauth_token_secret']);
            // Set Twitter API version to 1.1.
            $connection->host = "https://api.twitter.com/1.1/";
            $params = array('include_entities' => 'false');
            $content = $connection->get('account/verify_credentials', $params);    
            if($content && isset($content->screen_name) && isset($content->name))
            {
                $tweeterer = new Tweeterer();                
                $data = (object)array(
                    'twitter_id' => $content->id, 
                    'access_token' => $access_token['oauth_token'],
                    'access_token_secret' => $access_token['oauth_token_secret']);                
                $tweeterer->exists($content->id) 
                        ?$tweeterer->update($data) 
                        : $tweeterer->create($data);
                echo '{"status": "ok", "message": {"text": "Laplacianlab\'s SDSS can '
                . 'now access your Twitter account on your behalf. Please, if you no '
                . 'longer want this, log in your Twitter account and revoke access."}}';        
                session_destroy();
            }
            else
            {
                throw new Exception('Login error.');                
            }
        }
    } 
    else
    {
        throw new Exception('Login error.');
    }
});

/**
 * Run Slim!
 */
$app->run();

2.2. MySQL OOP ラッパー

ここでアプリケーションのモデルディレクトリ内 に PHP クラスの Tweeterer.php および EA.php を作成する必要があります。実際のモデル層を作成するというよりは、われわれのやっていることは MySQL テーブルを1つのオブジェクト指向クラスにラップすることです。

model\Tweeterer.php:

'DBConnection.php';
/**
 * Tweeterer's simple OOP wrapper
 *
 * @author      Jordi Bassagañas
 * @copyright   2014 Jordi Bassagañas
 * @link        https://www.mql5.com/ja/users/laplacianlab
 */
class Tweeterer
{   
    /**
     * @var string MySQL table
     */
    protected $table = 'twitterers';
    /**
     * Gets the user's OAuth tokens
     * @param integer $id
     * @return stdClass OAuth tokens: access_token and access_token_secret
     */
    public function getTokens($id)
    {
        $sql = "SELECT access_token, access_token_secret FROM $this->table WHERE id=$id";
        return DBConnection::getInstance()->query($sql)->fetch_object();        
    }    
    /**
     * Checks if it's been long enough so that the tweeterer can tweet again
     * @param string $timeLastTweet e.g. 2014-07-05 15:26:49
     * @param string $timeWindow A time window, e.g. 1 hour
     * @return boolean
     */
    public function canTweet($timeLastTweet=null, $timeWindow=null)
    {
        if(!isset($timeLastTweet)) return true;
        $diff = time() - strtotime($timeLastTweet);
        switch($timeWindow)
        {
            case '1 minute';                
                $diff <= 60 ? $canTweet = false : $canTweet = true;                
                break;                
            case '1 hour';                
                $diff <= 3600 ? $canTweet = false : $canTweet = true;                
                break;                
            case '1 day':                                
                $diff <= 86400 ? $canTweet = false : $canTweet = true;                
                break;
            default:                
                $canTweet = false;                
                break;                
        } 
        if($canTweet)
        {
            return true;
        }
        else 
        {
            throw new Exception('Please wait until the time window has elapsed.');
        }
    }
    /**
     * Adds a new signal
     * @param type $id_twitterer
     * @param stdClass $data
     * @return integer The new row id
     */
    public function addSignal($id_twitterer, stdClass $data)
    {
        $sql = 'INSERT INTO signals(id_ea, id_twitterer, symbol, operation, value) VALUES (' 
            . $data->id_ea . ","
            . $id_twitterer . ",'"    
            . $data->symbol . "','"
            . $data->operation . "',"
            . $data->value . ')'; 
        DBConnection::getInstance()->query($sql);        
        return DBConnection::getInstance()->getHandler()->insert_id;  
    }
    /**
     * Checks whether the given twitterer exists
     * @param string $id
     * @return boolean
     */
    public function exists($id)
    {
        $sql = "SELECT * FROM $this->table WHERE twitter_id='$id'";        
        $result = DBConnection::getInstance()->query($sql);        
        return (boolean)$result->num_rows;
    }    
    /**
     * Creates a new twitterer
     * @param stdClass $data
     * @return integer The new row id
     */
    public function create(stdClass $data)
    {
        $sql = "INSERT INTO $this->table(twitter_id, access_token, access_token_secret) "
            . "VALUES ('"
            . $data->twitter_id . "','"
            . $data->access_token . "','"
            . $data->access_token_secret . "')";        
        DBConnection::getInstance()->query($sql);
        return DBConnection::getInstance()->getHandler()->insert_id;
    }    
    /**
     * Updates the twitterer's data
     * @param stdClass $data
     * @return Mysqli object
     */
    public function update(stdClass $data)
    {
        $sql = "UPDATE $this->table SET "
            . "access_token = '" . $data->access_token . "', "
            . "access_token_secret = '" . $data->access_token_secret . "' "
            . "WHERE twitter_id ='" . $data->twitter_id . "'";        
        return DBConnection::getInstance()->query($sql);
    }    
    /**
     * Gets the last trading signal sent by the twitterer
     * @param type $id The twitterer id
     * @return mixed The last trading signal
     */
    public function getLastSignal($id)
    {
        $sql = "SELECT * FROM signals WHERE id_twitterer=$id ORDER BY id DESC LIMIT 1";
        $result = DBConnection::getInstance()->query($sql);
        if($result->num_rows == 1)
        {
            return $result->fetch_object();
        }
        else
        {
            $signal = new stdClass;
            $signal->created_at = null;
            return $signal;
        }
    }
}

model\EA.php:

'DBConnection.php';
/**
 * EA's simple OOP wrapper
 *
 * @author      Jordi Bassagañas
 * @copyright   2014 Jordi Bassagañas
 * @link        https://www.mql5.com/ja/users/laplacianlab
 */
class EA
{   
    /**
     * @var string MySQL table
     */
    protected $table = 'eas';
    /**
     * Gets an EA by id
     * @param integer $id
     * @return stdClass
     */
    public function get($id)
    {
        $sql = "SELECT * FROM $this->table WHERE id=$id";     
        return DBConnection::getInstance()->query($sql)->fetch_object();
    }
}

model\DBConnection.php:

/**
 * DBConnection class
 * 
 * @author      Jordi Bassagañas
 * @copyright   2014 Jordi Bassagañas
 * @link        https://www.mql5.com/ja/users/laplacianlab
 */
class DBConnection 
{ 
    /**
     * @var DBConnection Singleton instance
     */
    private static $instance;
    /**
     * @var mysqli Database handler
     */
    private $mysqli;
    /**
     *  Opens a new connection to the MySQL server
     */
    private function __construct()
    { 
        mysqli_report(MYSQLI_REPORT_STRICT);
        try {
            $this->mysqli = new MySQLI(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME); 
        } catch (Exception $e) {        
            throw new Exception('Unable to connect to the database, please, try again later.');
        }
    } 
    /**
     * Gets the singleton instance
     * @return type
     */
    public static function getInstance()
    {
        if (!self::$instance instanceof self) self::$instance = new self; 
        return self::$instance;
    } 
    /**
     * Gets the database handler
     * @return mysqli
     */
    public function getHandler()
    { 
        return $this->mysqli; 
    } 
    /**
     * Runs the given query
     * @param string $sql
     * @return mixed
     */
    public function query($sql)
    {
        $result = $this->mysqli->query($sql);
        if ($result === false)
        {       
            throw new Exception('Unable to run query, please, try again later.');
        }
        else
        {
            return $result;
        }
    } 
}


おわりに

本稿パート1でご紹介した SDSS のクライアント側を作成し、この決定に従いサーバー側を形成しました。最終的に新しい MQL5 のネイティブ関数 WebRequest() を使用しました。この特定のソリューションの賛成点と反対点については、WebRequest() iはもともとウェブサービスを使用するのではなく、ウェブページに対して GET と POST リクエストを作るためのものでした。ただし、同時にこの新しい機能を使用することにしました。というのも一からカスタムコンポーネントを作成するより安全だからです。

MQL5 クライアントと PHP サーバーの間に真に REST なダイアログを確立することがより洗練されているでしょう。しかしわれわれの特定のニーズにはWebRequest() を適用することの方が簡単だったのです。このためウェブサービスは URL エンコードデータを受信し、それを PHP が管理できるフォーマットに変換します。

私は今現在このシステムで作業をしています。今では私は個人的なトレードシグナルをツイートすることができます。それは機能的で、単独のユーザーの役に立ちますが、現実のプロダクション環境で完全に動作するにはそろっていないピースがいくつかあります。例として、はデータベースに依存しないフレームワークです。そのためSQLインジェクションを気にする必要があります。MetaTrader 5 ターミナルと PHP アプリケーション間の通信を確保する方法についても説明はしていません。よって本稿で提案されているようにはこのアプリを現実の環境で実行しないでください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/1044

添付されたファイル |
database.txt (1.32 KB)
dummy_ontimer.mq5 (1.36 KB)
Dummy.mq5 (3.27 KB)
index.txt (6.08 KB)
Tweeterer.txt (4.59 KB)
EA.txt (0.58 KB)
DBConnection.txt (1.54 KB)
ソーシャルテクノロジースタートアップの構築 パート1: MetaTrader 5 シグナルをツイートする ソーシャルテクノロジースタートアップの構築 パート1: MetaTrader 5 シグナルをツイートする
今日は MetaTrader 5 ターミナルを Twitter とリンクする方法を学習し、EA のトレードシグナルをツイートできるようにします。RESTful ウェブサービスに基づく PHP にソーシャルディシジョン支援システムを作成します。この考えはコンピュータ援用取引と呼ばれる自動トレーディングの特定の概念からきています。われわれは 別の方法でExpert Advisors によって自動でマーケットに出されるトレードシグナルをフィルターにかける人間のトレーダーの認知能力を欲しています。
MQL5.com フリーランス:開発者の収入源(インフォグラフィック) MQL5.com フリーランス:開発者の収入源(インフォグラフィック)
「MQL5 フリーランスサービス」の4周年を記念して、これまでのサービス結果を示すインフォグラフィックを作成しました。数字は自らを語ります:現在まで合計約 $600,000 に相当する 10,000 を越える注文が実行されるかたわら、 3,000 人の顧客と 300 人の開発者がすでにこのサービスを利用しました。
Johnpaul77 シグナル提供者:「われわれの戦略は3年以上利益をあげている。なぜ変えなくちゃならないんだ?」 Johnpaul77 シグナル提供者:「われわれの戦略は3年以上利益をあげている。なぜ変えなくちゃならないんだ?」
小さな秘密を明かします。:MQL5.com ウェブサイトのビジターはほとんどの時間をJohnpaul77 のシグナルページで費やします。それは実アカウントでトータル570万ドルの資金を有する、定期購読者約900人のわれわれのシグナルレーティングのリーダーです。シグナルの提供者にインタービューします。そこには4人の人物がいることがわかりました。チームメンバー間で任務はどのように割り当てされているのでしょうか?どのようなテクニカルツールを使用しているのでしょうか?なぜかれらは自分達を John Paul と呼ぶのでしょうか?そして最後にインドネシア出身の一般的なゲーマーが MQL5.comでトップシグナルの提供者になったのでしょうか?本稿ですべての答えを見つけてください。
MQL5.comのフリーランスのお仕事 - 開発者のお気に入りの場所 MQL5.comのフリーランスのお仕事 - 開発者のお気に入りの場所
トレーディングシステムの開発者は、エキスパートアドバイザーを必要とするトレーダーに彼らのサービスをマーケティングする必要はありません - 彼らが探してくれるのです。すでに、何千ものトレーダーがMQL5のフリーランス開発者に注文を頼み、MQL5.comにて作業に支払いを行っています。4年間、このサービスは10000以上もの仕事に対して累計3000人のトレーダーが支払えるようにしてきました。そして、トレーダーと開発者の活動は常に拡大しています。