Construire une start-up de technologie sociale, Deuxième partie : Programmation d'un client REST MQL5
Introduction
Dans la partie précédente de cet article, nous avons présenté l'architecture d'un soi-disant système d'aide à la décision sociale. D'une part, ce système se compose d'un terminal MetaTrader 5 envoyant les décisions automatiques des Expert Advisors vers le serveur. De l'autre côté de la communication, il y a une application Twitter construite sur le cadre du Slim PHP qui reçoit ces signaux de trading, les stocke dans une base de données MySQL, et enfin les tweeter vers les gens. L'objectif principal du SDSS est d'enregistrer les actions humaines effectuées sur des signaux robotiques et de prendre des décisions humaines en conséquence. Cela est possible car les signaux robotiques peuvent être ainsi exposés à un très large public d'experts.
Dans cette deuxième partie, nous allons développer le côté client du SDSS avec le langage de programmation MQL5. Nous discutons de certaines alternatives et identifions les avantages et les inconvénients de chacune d'entre elles. Ensuite, nous assemblerons toutes les pièces du puzzle et finirons par façonner l'API PHP REST qui reçoit les signaux de trading des Expert Advisors. Pour ce faire, nous devons prendre en compte certains aspects impliqués dans la programmation côté client.
Vous pouvez maintenant tweeter vos signaux de trading MQL5 !
1. Le côté client du SDSS
1.1. Tweeter certains signaux de trading dans l’évènement OnTimer
J'ai envisagé de montrer comment les signaux de trading sont envoyés à partir de l'événement OnTimer pour des problèmes de simplicité. Après avoir vu comment fonctionne cet exemple simple, il sera très facile d'extrapoler ce comportement de base vers un Expert Advisor ordinaire.
dummy_ontimer.mq5 :
#property copyright "Author: laplacianlab, CC Attribution-Noncommercial-No Derivate 3.0" #property link "https://www.mql5.com/en/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)); } }
Comme vous pouvez le voir, la partie centrale de cette application cliente est la nouvelle fonction WebRequest de MQL5.
Programmer un composant MQL5 personnalisé pour gérer la communication HTTP serait une alternative à cette solution, mais déléguer cette tâche à MetaQuotes via cette nouvelle fonctionnalité de langue est plus sûre.
Le programme MQL5 ci-dessus génère les éléments suivants :
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."}}
Veuillez noter que le serveur répond avec ce message :
{"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
En effet, il existe un petit mécanisme de sécurité implémenté dans le signal / l’ajout de la méthode API pour empêcher le SDSS des robots scalpeurs hyperactifs :
/** * 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."}}'; } });
Le mécanisme simple ci-dessus entre en jeu dans l'application Web, juste après que le serveur Web ait déjà vérifié que la requête HTTP entrante n'est pas malveillante (par exemple, le signal entrant n'est pas une attaque par déni de service).
Le serveur Web peut être responsable de la prévention de telles attaques. À titre d'exemple, Apache peut les empêcher en combinant les modules evasive et security.
Il s'agit d'une configuration mod_evasive typique d'Apache où l'administrateur du serveur peut contrôler les requêtes HTTP que l'application peut accepter par seconde, etc.
<IfModule mod_evasive20.c> DOSHashTableSize 3097 DOSPageCount 2 DOSSiteCount 50 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 60 DOSEmailNotify someone@somewhere.com </IfModule>
Donc, comme on le dit, le but de la méthode PHP canTweet est de bloquer les scalpeurs hyperactifs qui ne sont pas considérés comme des attaques HTTP par le SDSS. La méthode canTweet est implémentée dans la classe Twetterer (qui sera discutée plus loin) :
/** * 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.'); } }
Voyons maintenant quelques champs d'en-tête de requête HTTP que WebRequest crée automatiquement pour nous :
Content-Type: application/x-www-form-urlencoded Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Le POST de WebRequest suppose que les programmeurs souhaitent envoyer des données de formulaire HTML, néanmoins dans ce scénario, nous voudrions envoyer au serveur les champs d'en-tête de requête HTTP suivants :
Content-Type: application/json Accept: application/json
Comme il n'y a pas de solution miracle, nous devons être cohérents avec notre décision et étudier en profondeur comment WebRequest répond à nos exigences afin de découvrir les avantages et les inconvénients.
Il serait plus correct d'un point de vue technique d'établir de véritables dialogues HTTP REST, mais comme nous l'avons dit, c'est une solution plus sûre de déléguer des dialogues HTTP à MetaQuotes même si WebRequest() semble être à l'origine destiné aux pages web, et non aux services web. C'est pour cette raison que nous finirons par encoder l'url du signal de trading du client. L'API recevra les signaux codés par l’url, puis les convertira au format stdClass de PHP.
Une alternative à l'utilisation de la fonction WebRequest() consiste à écrire un composant MQL5 personnalisé fonctionnant à un niveau proche du système d'exploitation à l'aide de la bibliothèque wininet.dll. Les articles Utilisation de WinInet.dll pour l'échange de données entre terminaux via Internet et Utilisation de WinInet dans MQL5. Deuxième partie : Les requêtes et fichiers POST expliquent les principes fondamentaux de cette approche. Cependant, l'expérience des développeurs MQL5 et de la communauté MQL5 a montré que cette solution n'est pas aussi simple qu'elle paraît à première vue. Cela présente l'inconvénient que les appels aux fonctions WinINet peuvent être interrompus lors de la mise à niveau de MetaTrader.
1.2. Tweeter les signaux de trading d'un EA
Maintenant, extrapolons ce que nous avons récemment expliqué. J'ai créé le robot factice suivant afin d'illustrer le problème du scalping contrôlé et des attaques par déni de service.
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/en/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); }
Le code ci-dessus ne peut pas être plus simple. Cet Expert Advisor ne place qu'une seule position courte sur chaque coche. Pour cette raison, il est très probable que ce robot finisse par placer de nombreuses positions dans un court intervalle de temps, surtout si vous l'exécutez à un moment où il y a beaucoup de volatilité. Il n'y a aucune raison de s'inquiéter. Le côté serveur contrôle l'intervalle de tweet en configurant le serveur web pour empêcher les attaques DoS et en définissant une certaine fenêtre de temps dans l'application PHP, comme expliqué.
Tout cela étant clair, vous pouvez maintenant utiliser la fonction Tweet de cet EA et la placer dans votre Expert Advisor préféré.
1.3. Comment les utilisateurs voient-ils leurs signaux de trading tweetés ?
Dans l'exemple suivant, @laplacianlab autorise le SDSS à tweeter les signaux de l'EA factice qui ont été publiés dans la section précédente :
Figure 1. @laplacianlab a autorisé le SDSS à tweeter en son nom
D’ailleurs, le nom Bollinger Bands apparaît dans cet exemple car c'est celui que nous avons stocké dans la base de données MySQL dans la première partie de cet article. id_ea=1 était associé à "Bollinger Bands", mais nous aurions dû le changer en quelque chose comme "Dummy" afin de bien correspondre à cette explication. En tout cas c'est un aspect secondaire mais désolé pour ce petit désagrément.
La base de données MySQL est finalement la suivante :
# 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. Le côté serveur du SDSS
Avant de continuer à façonner le côté serveur de notre système d'aide à la décision sociale, rappelons-nous brièvement que nous avons actuellement la structure de répertoire suivante :
Figure 2. Structure de répertoire de l'API PHP basée sur Slim
2.1. Code API PHP
D'après ce qui a été expliqué, le fichier index.php devrait maintenant ressembler à ceci :
<?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/en/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. Wrappers de POO MySQL
Il faut maintenant créer les classes PHP Tweeterer.php et EA.php dans le répertoire model de l'application Slim. Veuillez noter qu'au lieu de développer une couche de modèle réelle, nous enveloppons les tables MySQL dans de simples classes orientées par objet.
model\Tweeterer.php :
<?php require_once 'DBConnection.php'; /** * Tweeterer's simple OOP wrapper * * @author Jordi Bassagañas * @copyright 2014 Jordi Bassagañas * @link https://www.mql5.com/en/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 :
<?php require_once 'DBConnection.php'; /** * EA's simple OOP wrapper * * @author Jordi Bassagañas * @copyright 2014 Jordi Bassagañas * @link https://www.mql5.com/en/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 :
<?php /** * DBConnection class * * @author Jordi Bassagañas * @copyright 2014 Jordi Bassagañas * @link https://www.mql5.com/en/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; } } }
Conclusion
Nous avons développé le côté client du SDSS qui a été présenté dans la première partie de cet article, et avons fini par façonner le côté serveur en fonction de cette décision. Nous avons enfin utilisé la nouvelle fonction native de MQL5 WebRequest(). Concernant les avantages et les inconvénients de cette solution spécifique, nous avons vu que WebRequest() n'est pas destiné à l'origine à consommer des services web, mais à effectuer des requêtes GET et POST vers des pages web. Cependant, dans le même temps, nous avons décidé d'utiliser cette nouvelle fonctionnalité car elle est plus sûre que de développer un composant personnalisé à partir de zéro.
Il aurait été plus élégant d'établir de véritables dialogues REST entre le client MQL5 et le serveur PHP, mais il a été beaucoup plus simple d'adapter WebRequest() à notre besoin spécifique. Ainsi, le service web reçoit des données codées en URL et les convertit dans un format gérable pour PHP.
Je travaille actuellement sur ce système. Pour l'instant, je peux tweeter mes signaux de trading personnels. Il est opérationnel, il fonctionne pour un seul utilisateur, mais il manque quelques pièces pour qu'il fonctionne complètement dans un environnement de production réel. Par exemple, Slim est une infrastructure logicielle indépendante de la base de données, vous devez donc vous soucier des injections SQL. Nous n'avons pas non plus expliqué comment sécuriser la communication entre le terminal MetaTrader 5 et l'application PHP, veuillez donc ne pas exécuter cette application dans un environnement réel tel qu'il est présenté dans cet article.
Traduit de l’anglais par MetaQuotes Ltd.
Article original : https://www.mql5.com/en/articles/1044
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation