English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Construindo uma Startup em Tecnologia Social, Parte II: Programando um cliente REST em MQL5

Construindo uma Startup em Tecnologia Social, Parte II: Programando um cliente REST em MQL5

MetaTrader 5Negociação | 1 setembro 2014, 16:15
3 349 0
laplacianlab
[Excluído]

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!

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 dá permissão para o SDSS tuitar em seu nome

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

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

Arquivos anexados |
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)
Indicador para Construção do Gráfico "Ruptura de Três Linhas" Indicador para Construção do Gráfico "Ruptura de Três Linhas"
Este artigo é dedicado ao gráfico "Ruptura de Três Linhas" sugerido por Steve Nison em seu livro "Beyond Candlesticks". A maior vantagem deste gráfico é que ele permite filtrar pequenas flutuações de preço, em relação ao movimento precedente. Nós vamos discutir os princípios da construção do gráfico, o código do indicador e alguns exemplos de estratégias de negociação baseadas nele.
Construindo uma Startup em Tecnologia Social, Parte I: Tuíte seus Sinais do MetaTrader 5 Construindo uma Startup em Tecnologia Social, Parte I: Tuíte seus Sinais do MetaTrader 5
Hoje vamos aprender a ligar um terminal MetaTrader 5 com o Twitter para que você possa "tuitar" os sinais de negociação de seus EAs. Estaremos desenvolvendo um Sistema de Apoio à Decisão Social em PHP com base no serviço web RESTful. Essa idéia vem de um conceito específico da negociação automatizada chamada de negociação assistida por computador. Nós queremos as habilidades cognitivas dos traders humanos para filtrar os sinais de negociação, que de outra maneira eles seriam colocadas automaticamente no mercado pelos Expert Advisors.
Como Preparar Sua Conta de Negociação para Migrar a Hospedagem Virtual Como Preparar Sua Conta de Negociação para Migrar a Hospedagem Virtual
O terminal cliente MetaTrader é perfeito para a automação de estratégias de negociação. Ele possui todas as ferramentas que os desenvolvedores de robôs de negociação necessitam - Uma poderosa linguagem de programação em MQL4 / MQL5, baseada em C++, um conveniente ambiente de desenvolvimento chamado MetaEditor e um testador de estratégia multi segmentado (multi-threaded) que suporta a computação distribuída MQL5 Cloud Network. Neste artigo, você irá descobrir como mover o seu terminal cliente para o ambiente virtual com todos os elementos personalizados.
Como desenvolvemos o serviço de Sinais MetaTrader e Negociação Social Como desenvolvemos o serviço de Sinais MetaTrader e Negociação Social
Continuamos a melhorar o serviço de Sinais, melhorando os mecanismos, adicionando novas funções e corrigindo falhas. Os Serviço de Sinais MetaTrader de 2012 e o atual Serviço de Sinais MetaTrader são dois serviços completamente diferentes. Atualmente estamos implementando um serviço de Hospedagem Virtual de Nuvens (Virtual Hosting Cloud) que consiste numa rede de servidores para suportar versões específicas do terminal de cliente MetaTrader.