Construindo uma Startup em Tecnologia Social, Parte II: Programando um cliente REST em MQL5
Introdução
Na parte anterior deste artigo, apresentamos a arquitetura do Sistema de Apoio à Decisão social (SDSS). Por um lado, este sistema é constituído pelo terminal MetaTrader 5 que envia as decisões automatizadas dos Expert Advisors para o servidor. Do outro lado da comunicação, existe um aplicativo do Twitter construído sobre a estrutura Slim PHP que recebe os sinais de negociação, armazena-os em um banco de dados MySQL, e, finalmente, os tuita para as pessoas. O principal objetivo do SDSS é registrar as ações humanas realizadas nos sinais robóticos e tomar decisões humanas em conformidade. Isto é possível porque os sinais robóticos pode ser expostos desta forma para uma audiência muito grande de Experts.
Nesta segunda parte, vamos desenvolver o lado do cliente SDSS com a linguagem de programação MQL5. Estaremos discutindo algumas alternativas, bem como identificar os prós e os contras de cada um deles. Então, mais tarde, vamos juntar todas as peças do quebra-cabeça, e acabam por moldar a API REST em PHP que recebe sinais de negociação dos Expert Advisors. Para conseguir isso, devemos levar em conta alguns aspectos envolvidos na programação do lado do cliente.
Agora você pode tuitar seus sinais de negociação MQL5!
1. O lado do cliente SDSS
1.1. Tuitar alguns sinais de negociação no Evento OnTimer
Eu levei em consideração em mostrar como os sinais de negociação são enviados a partir do evento OnTimer por questões de simplicidade. Depois de ver como este exemplo simples funciona, será muito fácil para extrapolar esse comportamento do núcleo para um Expert Advisor normal.
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)); } }
Como você pode ver, a parte central do aplicativo cliente é a nova função WebRequest em MQL5.
Programar um componente MQL5 personalizado para lidar com a comunicação HTTP seria uma alternativa a esta solução, mas delegando essa tarefa para MetaQuotes através deste novo recurso da linguagem é mais seguro.
O programa MQL5 acima exibe a seguinte saída:
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."}}
Por favor, note que o servidor responde com a seguinte mensagem:
{"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
Isso ocorre porque há um pequeno mecanismo de segurança implementado no método API signal/add para evitar o SDSS de robôs scalper hiperativos:
/** * 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."}}'; } });
O simples mecanismo acima entra em jogo dentro do aplicativo web, somente após o servidor web ter confirmado que a solicitação HTTP de entrada não é maliciosa (por exemplo, o sinal de entrada não é nenhum ataque de negação de serviço).
O servidor web pode ser responsável por evitar tais ataques. Como um exemplo, o Apache pode impedi-los combinando os módulos evasivos e de segurança.
Esta é a configuração Apache típica do mod_evasive em que o administrador do servidor pode controlar as solicitações HTTP que o aplicativo pode aceitar por segundo, etc
<IfModule mod_evasive20.c> DOSHashTableSize 3097 DOSPageCount 2 DOSSiteCount 50 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 60 DOSEmailNotify someone@somewhere.com </IfModule>
Então, como nós dissemos, o objetivo do método PHP canTweet é bloquear scalpers hiperativos que não são considerados como ataques HTTP pelo SDSS. O método é canTweet implementado na classe Twetterer (que será discutido mais tarde):
/** * 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.'); } }
Agora vamos ver alguns campos do cabeçalho de solicitação HTTP que o WebRequest automaticamente constrói para nós:
Content-Type: application/x-www-form-urlencoded Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
O método POST de WebRequest assume que os programadores desejam enviar alguns dados do tipo HTML, no entanto, neste cenário nós gostariamos de enviar ao servidor as seguintes campos de cabeçalho de solicitação HTTP:
Content-Type: application/json Accept: application/json
Como não há balas de prata, devemos ser coerentes com a nossa decisão e estudar minuciosamente como o WebRequest se adapta às nossas necessidades, a fim de descobrir os prós e contras.
Seria mais correto, do ponto de vista técnico, estabelecer verdadeiros diálogos HTTP em REST, mas como dissemos, é uma solução mais segura em delegar os diálogos HTTP para a MetaQuotes embora que o WebRequest() pareça ser originalmente planejado para páginas da web, e não para serviços web. É por esta razão que vamos acabar codificando a URL do sinal de negociação do cliente. O API irá receber a URL do sinal codificada e, em seguida, irá convertê-los para o formato stdClass do PHP.
Uma alternativa à utilização da função WebRequest() é escrever um componente MQL5 personalizado que trabalha em um nível próximo do sistema operacional usando a biblioteca wininet.dll. Os artigos Utilizando WinInet.dll para a troca de dados entre plataformas via internet e Usar WinInet em MQL5. Parte 2: Solicitações POST e Arquivos explicam os fundamentos dessa abordagem. No entanto, a experiência dos desenvolvedores em MQL5 e da Comunidade MQL5 mostrou que esta solução não é tão fácil como pode parecer à primeira vista. Ele apresenta a desvantagem de que as chamadas para as funções do WinInet podem ser quebradas quando o MetaTrader é atualizado.
1.2. Tuitando os Sinais de Negociação de um EA
Agora vamos extrapolar o que nós explicamos recentemente. Eu criei o seguinte robô fictício, a fim de ilustrar o problema sobre o escalpelamento controlado e os ataques de negação de serviço.
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); }
O código acima não pode ser mais fácil. Este Expert Advisor apenas coloca uma única posição vendida a cada tick. Por esta razão, é muito provável que este robô acabe colocando muitas posições em um curto intervalo de tempo, especialmente se você executá-lo em um momento do tempo em que há muita volatilidade. Não há nenhuma razão para se preocupar. O lado do servidor controla o intervalo de tuitação para ambos como configurar o servidor web para evitar ataques de negação de serviço, e para definir uma determinada janela de tempo na aplicação PHP, como explicado.
Com tudo isso claro, agora você pode pegar a função Tweet deste EA e colocá-lo em seu Expert Advisor favorito.
1.3. Como os usuários vêem os seus sinais de negociação tuitados?
No exemplo a seguir, @laplacianlab dá permissão para o SDSS tuitar os sinais do EA fictício, que foi publicado na seção anterior:
Figura 1. @laplacianlab deu permissão para o SDSS tuitar em seu nome
A propósito, o nome Bollinger Bands aparece neste exemplo, porque isso é o que nós armazenamos no banco de dados MySQL na primeira parte deste artigo. id_ea=1 foi associado a "Bollinger Bands", mas nós deveríamos ter mudado isso para algo como o "Fictício", a fim de se encaixar bem nessa explicação. Em qualquer caso, este é um aspecto secundário, mas me desculpe por este pequeno inconveniente.
The MySQL database is finally as follows:
# 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. O Lado do Servidor SDSS
Antes de continuar a moldar o lado do servidor do nosso Sistema de Apoio à Decisão social, vamos lembrá-lo brevemente que nós temos a seguinte estrutura de diretórios no momento:
Figura 2. Estrutura de diretórios do API PHP baseado em Slim
2.1. O Código API em PHP
De acordo com o que foi explicado, o arquivo index.php deve ter esta aparência agora:
<?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 em POO MySQL
Agora temos de criar as classes PHP Tweeterer.php e o EA.php no diretório modelo da aplicação Slim. Por favor, note que ao invés de desenvolver uma camada do modelo real o que nós fazemos foi envolver as tabelas do MySQL em simples classes orientadas a objetos.
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; } } }
Conclusão
Nós desenvolvemos o lado do cliente SDSS que foi introduzido na primeira parte deste artigo, e acabamos por moldar o lado do servidor de acordo com esta decisão. Nós finalmente usamos a nova função nativa WebRequest() em MQL5. Em relação aos prós e contras desta solução específica, vimos que a WebRequest() não é destinada originalmente a consumir serviços Web, mas para se fazer solicitações do tipo GET e POST para as páginas da web. No entanto, ao mesmo tempo, decidimos usar este novo recurso, porque é mais seguro do que o desenvolvimento de um componente personalizado a partir do zero.
Teria sido mais elegante estabelecer verdadeiros diálogos em REST entre o cliente MQL5 e o servidor PHP, mas foi muito mais fácil adaptar a WebRequest() para a nossa necessidade específico. Assim, o serviço web recebe a URL de dados codificados URL e os converte em um formato viável para PHP.
Eu estou atualmente trabalhando neste sistema. Por enquanto eu posso tuitar meus sinais de negociação pessoais. Ele é funcional, ele trabalha para um único usuário, mas existem algumas peças faltando para que ele funcione completamente em um ambiente de produção real. Como exemplo, o Slim é uma estrutura de banco de dados agnóstica, então você deve se preocupar com injeções SQL. Como não explicamos como proteger a comunicação entre o terminal MetaTrader 5 e a aplicação em PHP, por favor, não execute este aplicativo em um ambiente real, como foi apresentado neste artigo.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/1044
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso