English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Desarrollo de una Startup social tecnológica, Parte II: Programamos el cliente REST en MQL5

Desarrollo de una Startup social tecnológica, Parte II: Programamos el cliente REST en MQL5

MetaTrader 5Trading | 26 septiembre 2014, 10:00
1 146 0
laplacianlab
[Eliminado]

Introducción

En la primera parte del artículo hemos presentado Social Decision Support System (Sistema social del soporte para la toma de decisiones) o SDSS. En un lado, el terminal MetaTrader 5 envía al servidor las decisiones del EA tomadas automáticamente. En otro lado, la aplicación del Twitter construida sobre el framework Slim PHP recibe estas señales, las guarda en la base de datos MySQL y luego las tuitea en la cuenta del Twitter. El principal objetivo del SDSS consiste en el registro de las acciones humanas respecto a las señales del robot y la toma de decisiones correspondientes. Es posible porque las señales del robot pueden estar disponibles para un auditorio de expertos bastante grande.

En esta parte vamos a desarrollar el lado del cliente del SDSS utilizando el lenguaje MQL5. Discutiremos las posibles opciones, así como sus ventajas e inconvenientes. Al final reuniremos todas las partes sueltas y formaremos la aplicación de API REST en PHP que recibe las señales comerciales del EA. Para cumplir nuestra tarea, tenemos que tomar en cuenta algunos aspectos de programación del lado del cliente.

¡Ahora Usted puede tuitear sus señales comerciales MQL5 en su cuenta del Twitter!

¡Ahora Usted puede tuitear sus señales comerciales MQL5 en su cuenta del Twitter!


1. Lado del Cliente del SDSS

1.1. Publicación en el Twitter de las señales comerciales en el procesador de eventos OnTimer

Para que sea más claro, he decidido demostrar cómo se envían las señales comerciales desde el procesador de eventos OnTimer. Después de que vean el principio de funcionamiento en un simple ejemplo, podrán extrapolarlo con facilidad a su EA.

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()
  {
//--- variables HTTP del cliente REST
   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);
//--- reseteamos el último error
   ResetLastError();
//--- enviamos los datos a API REST
   res=WebRequest("POST",uri,NULL,NULL,50,post,ArraySize(post),result,headers);
//--- comprobamos los errores
   if(res==-1)
     {
      Print("Código del error =",GetLastError());
      //--- es posible que URL no haya sido añadido, mostramos el mensaje para añadirlo
      MessageBox("Añadir la dirección '"+uri+"' en la pestaña Expert Advisors de la ventana Options","Error",MB_ICONINFORMATION);
     }
   else
     {
      //--- con éxito
      Print("POST del cliente REST: ",signal);
      Print("Respuesta del servidor: ",CharArrayToString(result,0,-1));
     }         
  }

Como pueden ver, la parte central de esta aplicación cliente está ocupada por la nueva función MQL5 WebRequest.

La programación de un componente personalizado MQL5 para comunicarse vía HTTP sería una alternativa a esta solución. Sin embargo, sería más seguro acudir a la solución de MetaQuotes utilizando la nueva función del lenguaje.

El programa MQL5 mencionado antes muestra lo siguiente:

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": "Por favor, espere que expire el intervalo de tiempo."}}
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": "Por favor, espere que expire el intervalo de tiempo."}}
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": "Por favor, espere que expire el intervalo de tiempo."}}
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": "Por favor, espere que expire el intervalo de tiempo."}}
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": "Por favor, espere que expire el intervalo de tiempo."}}
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": "Por favor, espere que expire el intervalo de tiempo."}}
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": "Por favor, espere que expire el intervalo de tiempo."}}

Fíjense en que la respuesta del servidor es la siguiente:

{"status": "ok", "message": {"text": "Por favor, espere que expire el intervalo de tiempo."}}

Eso ocurre porque se activa un pequeño mecanismo de seguridad incorporado en el método API signal/add con el fin de proteger el SDSS de los robots de scalping hiperactivos:

/**
 * Método REST.
 * Añade y tuitea en el Twitter la nueva señal comercial.
 */
$app->post('/signal/add', function() {
    $tweeterer = new Tweeterer();
    // Esta condición es un simple mecanismo que protege de los robots de scalping hiperactivos
    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."}}';
    }   
});

Este simple mecanismo se activa en la aplicación web en cuanto el servidor web confirme que la petición entrante HTTP no es perjudicial (por ejemplo, no es un ataque DoS).

El servidor web es capaz de evitar estos ataques. Por ejemplo, Apache lo hace mediante la combinación de los módulos evasive y security.

Aquí tiene una configuración típica mod_evasive de Apache donde el administrador del servidor puede controlar la cantidad de peticiones HTTP que puede aceptar la aplicación por segundo, etc.

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

Bien, como hemos dicho antes, el objetivo del método PHP canTweet consiste en bloquear los scalpers hiperactivos que no se determinan por el SDSS como ataques HTTP. El método canTweet está implementado en la clase Twetterer y será discutido más abajo:

/**
 * Comprueba si ha pasado suficiente tiempo para una nueva publicación del usuario en el Twitter
 * @param string $timeLastTweet e.g. 2014-07-05 15:26:49
 * @param string $timeWindow Intervalo de tiempo, por ejemplo, una hora
 * @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('Por favor, espere que expire el intervalo de tiempo.');
    }
}

Ahora vamos a ver algunos campos de cabeceras de las peticiones HTTP que WebRequest construye automáticamente para nosotros:

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

La petición POST de WebRequest supone que los programadores quieren enviar los datos en el formato HTML, sin embargo en este caso queremos enviar al servidor las siguientes cabeceras de la petición HTTP:

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

No hay ninguna panacea en este caso. Tenemos que ser consecuentes en nuestras decisiones y estudiar a fondo hasta qué punto WebRequest corresponde a nuestros requerimientos para sopesar todos los pros y los contras.

Desde el punto de vista técnico sería más correcto establecer los diálogos HTTP en REST, pero como ya hemos dicho antes la solución más segura consiste en delegar los diálogos HTTP a MetaQuotes a pesar de que WebRequest() estaba pensado desde el principio para las páginas web y no para los servicios web. Precisamente por esta razón vamos a acabar codificando URL de la señal comercial del cliente. La API va a recibir las señales codificadas en URL y convertirlas en el formato PHP stdClass.

La alternativa a la utilización de la función WebRequest() es escribir un componente MQL5 personalizado que trabaja a nivel próximo al sistema operativo que utiliza la librería wininet.dll. Los artículos Usar WinInet.dll para el intercambio de datos entre terminales por Internet y Usar WinInet en MQL5. Parte 2: solicitudes y archivos POST. explican los fundamentos de este enfoque. No obstante, la experiencia de la comunidad de los desarrolladores MQL5 muestra que esta solución no es tan sencilla como parece a primera vista. Existe una desventaja que consiste en que las llamadas a la función WinINet pueden quebrarse al actualizar MetaTrader.

1.2. Publicación en el Twitter de las señales comerciales del EA

Ahora vamos a aplicar todo de lo que estábamos hablando antes. He creado un modelo del robot para demostrar el problema del control del scalping y ataques del tipo "Denegación de Servicio" (DoS).

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)."
//+------------------------------------------------------------------+
//| Clase de trading                                                   |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Declaración de variables                                            |
//+------------------------------------------------------------------+
CPositionInfo PositionInfo;
CTrade trade;
MqlTick tick;
int stopLoss = 20;
int takeProfit = 20;
double size = 0.1;
//+------------------------------------------------------------------+
//| Nueva señal comercial                                            |
//+------------------------------------------------------------------+   
void Tweet(string uri, string signal)
  {
   char post[];
   char result[];
   string headers;
   int res;
   StringToCharArray(signal,post);
//--- reseteamos el último error
   ResetLastError();
//--- enviamos los datos a API REST
   res=WebRequest("POST",uri,NULL,NULL,50,post,ArraySize(post),result,headers);
//--- comprobamos los errores
   if(res==-1)
     {
      //--- error
      Print("Código del error =",GetLastError());
     }
   else
     {
      //--- con éxito
      Print("POST del cliente REST: ",signal);
      Print("Respuesta del servidor: ",CharArrayToString(result,0,-1));
     }         
  }
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {                
   return(0);  
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+  
void OnDeinit(const int reason)
  {  
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+  
void OnTick()
  {
//--- actualizamos los ticks
   SymbolInfoTick(_Symbol, tick);
//--- calculamos los niveles Take Profit y Stop Loss
   double tp;
   double sl;   
   sl = tick.ask + stopLoss * _Point;
   tp = tick.bid - takeProfit * _Point;
//--- abrimos la posición
   trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,size,tick.bid,sl,tp);
//--- señal comercial con URL codificado "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);
}

Este código ya no puede ser más simple. El EA abre una posición corta en cada tick. Por esta razón, lo más probable es que el robot abra muchas posiciones en un corto período de tiempo, sobre todo si Usted lo inicia en el momento de alta volatilidad. No obstante, no hay razones para preocuparse. El lado del servidor controla los intervalos de publicación tanto mediante la configuración del servidor web para la protección contra los ataques DoS, como mediante el establecimiento de un determinado intervalo de tiempo en la aplicación PHP, como se ha mencionado antes.

Hemos acabado con esta parte y ahora Usted puede añadir a su EA preferido la función de tuitear las señales comerciales en el Twitter.

1.3. ¿Cómo los usuarios ven sus señales tuiteados?

En el siguiente ejemplo @laplacianlab da permiso al SDSS tuitear las señales del modelo del robot expuesto en la sección anterior:

Fig. 1. @laplacianlab da permiso al SDSS para tuitear las señales en su nombre

Fig. 1. @laplacianlab ha dado el permiso al SDSS para tuitear las señales en su nombre

Por cierto, las Bandas de Bollinger se mencionan en este ejemplo porque hemos guardado este nombre en la base de datos MySQL en la primera parte del artículo. id_ea=1 correspondía a las "Bandas de Bollinger", pero tendríamos que cambiarlo por algo parecido a "Dummy" (modelo) para que correspondiera a nuestro ejemplo. En cualquier caso, esto tiene la importancia de segundo orden pero pido perdón por este pequeño inconveniente.

La base de datos MySQL se muestra a continuación:

# Creación de la base de datos MySQL

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;

# Descargamos los datos ...

# Como hemos mencionado en la primera parte, hay sólo una aplicación twitter

INSERT INTO eas(name, description) VALUES
('Bollinger Bands', '<p>Robot basado en las Bandas de Bollinger. Trabaja con los gráficos H4.</p>'),
('Two EMA', '<p>Robot basado en la intersección de dos Medias Móviles. Trabaja con los gráficos H4.</p>');


2. Lado del Servidor SDSS

Antes de ponerse a configurar nuestro Social Decision Support System, vamos a recordar que tenemos la siguiente estructura de carpetas:

Figura 2. Estructura de carpetas PHP API basada en Slim

Fig. 2. Estructura de carpetas PHP API basada en Slim

2.1. Código API en PHP

Conforme a lo dicho anteriormente, el archivo index.php ahora tiene el siguiente aspecto:

/**
 * Laplacianlab's SDSS - A REST API para tuitear las señales comerciales MQL5
 *
 * @author      Jordi Bassagañas
 * @copyright   2014 Jordi Bassagañas
 * @link        https://www.mql5.com/en/users/laplacianlab
 */

/* Lógica de autoarranque */
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();

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

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

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

/**
 * Método REST.
 * Página inicial.
 */
$app->get('/', function () {
    echo '{"status": "ok", "message": {"text": "Service available, please check API."}}';
});

/**
 * Método REST.
 * Añade y tuitea en el Twitter la nueva señal comercial.
 */
$app->post('/signal/add', function() {
    $tweeterer = new Tweeterer();
    // Esta condición es un simple mecanismo que protege contra los robots de scalping hiperactivos
    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."}}';
    }   
});

/**
 * Implementación de REST con TwitterOAuth.
 * Da permiso a Laplacianlab's SDSS tuitear las señales en nombre del usuario.
 * Por favor, siga el enlace 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 Excepción('Conexión con el Twitter fallida.');
                break;
            }
        }
        else 
        {
            throw new Excepción('Error de recepción del token de la solicitud.');
        }
    } 
    else 
    {    
        echo '{"status": "ok", "message": {"text": "Laplacianlab\'s SDSS puede '
        . 'obtener el acceso a la cuenta del Twitter en su nombre. Si no lo necesita más, '
        . 'autorícese en su cuenta del Twitter y revoque el acceso."}}';
    }    
});

/**
 * Implementación de REST con TwitterOAuth.
 * Es el retrollamado OAuth del método descrito más arriba. 
 * Almacena los tokens de acceso en la base de datos.
 * Por favor, siga el enlace 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']);
            // Establecemos la versión de Twitter API en 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 puede '
                . 'obtener el acceso a la cuenta del Twitter en su nombre. Si no lo necesita más, '
                . 'autorícese en su cuenta del Twitter y revoque el acceso."}}';        
                session_destroy();
            }
            else
            {
                throw new Excepción('Error de entrada.');                
            }
        }
    } 
    else
    {
        throw new Excepción('Error de entrada.');
    }
});

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

2.2. MySQL OOP Wrappers

Ahora vamos a crear las clases Tweeterer.php y EA.php con PHP en la carpeta de modelos de la aplicación Slim. Cabe mencionar que en vez de desarrollar la capa del modelo nosotros vamos a envolver las tablas MySQL en las clases orientadas a objetos.

model\Tweeterer.php:

'DBConnection.php';
/**
 * Envoltorio simple POO del Twitter
 *
 * @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';
    /**
     * Recibimos los tokens OAuth del usuario
     * @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();        
    }    
    /**
     * Comprueba si ha pasado suficiente tiempo para una nueva publicación del usuario en el Twitter
     * @param string $timeLastTweet e.g. 2014-07-05 15:26:49
     * @param string $timeWindow Inntervalo de tiempo, por ejemplo, 1 hora
     * @return boolean
     */
    public function canTweet($timeLastTweet=null, $timeWindow=null)
    {
        if(!isset($timeLastTweet)) return true;
        $diff = time() - strtotime($timeLastTweet);
        switch($timeWindow)
        {
            case '1 minuto';                
                $diff <= 60 ? $canTweet = false : $canTweet = true;                
                break;                
            case '1 hora';                
                $diff <= 3600 ? $canTweet = false : $canTweet = true;                
                break;                
            case '1 día':                                
                $diff <= 86400 ? $canTweet = false : $canTweet = true;                
                break;
            default:                
                $canTweet = false;                
                break;                
        } 
        if($canTweet)
        {
            return true;
        }
        else 
        {
            throw new Exception('Por favor, espere a que expire el intervalo de tiempo.');
        }
    }
    /**
     * Añadimos la nueva señal
     * @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;  
    }
    /**
     * Comprueba si existe el tuitero mencionado
     * @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;
    }    
    /**
     * Crea al nuevo tuitero
     * @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;
    }    
    /**
     * Actualiza los datos del tuitero
     * @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);
    }    
    /**
     * Recibe las últimas señales comerciales enviados por el tuitero
     * @param type $id The twitterer id
     * @return mixed Última señal comercial
     */
    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/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:

/**
 * 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;
    /**
     *  Abre la nueva conexión con el servidor MySQL 
     */
    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 Excepción('Falta la conexión con la base de datos. Por favor, vuelva a intentar.');
        }
    } 
    /**
     * Gets the singleton instance
     * @return type
     */
    public static function getInstance()
    {
        if (!self::$instance instanceof self) self::$instance = new self; 
        return self::$instance;
    } 
    /**
     * Recibe el manejador de las bases de datos
     * @return mysqli
     */
    public function getHandler()
    { 
        return $this->mysqli; 
    } 
    /**
     * Ejecuta esta solicitud
     * @param string $sql
     * @return mixed
     */
    public function query($sql)
    {
        $result = $this->mysqli->query($sql);
        if ($result === false)
        {       
            throw new Exception('No se puede ejecutar la solicitud. Por favor, vuelva a intentar.');
        }
        else
        {
            return $result;
        }
    } 
}

Conclusión

Hemos desarrollado el lado del cliente del SDSS que ha sido introducido en la primera parte del presente artículo y hemos terminado de formar el lado del servidor. Hemos utilizado la función built-in MQL5 WebRequest(). En cuanto a los pros y los contras de esta solución, hemos visto que la función WebRequest() no está destinada desde el principio para trabajar con los servicios web, sino para las solicitudes del tipo GET y POST de las páginas web. Al mismo tiempo, hemos decidido utilizarla porque es más seguro que desarrollar el componente personalizado desde cero.

Habría sido más bonito establecer los diálogos REST entre el cliente MQL5 y el servidor PHP, sin embargo, para nuestra determinada tarea ha resultado más fácil utilizar WebRequest(). De esta manera, el servicio web recibe los datos codificados en URL y los transforma en un formato conveniente para PHP.

Actualmente estoy trabajando en este sistema. En este momento puedo publicar mis señales comerciales en el Twitter. El sistema es totalmente funcional y funciona perfectamente para un único usuario pero todavía no se puede usarlo para tradear ya que faltan algunos componentes importantes. Por ejemplo, Slim es un framework que no depende del tipo de la base de datos por eso hay que ocuparse de la protección contra la introducción del código SQL. Además, no hemos discutido la cuestión de garantizar la interacción segura entre el terminal MetaTrader 5 y la aplicación PHP. Precisamente por esta razón la aplicación presentada en este artículo no puede ser utilizada para trabajar en condiciones reales.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/1044

Archivos adjuntos |
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)
Preparación de la cuenta comercial para la migración al hosting virtual Preparación de la cuenta comercial para la migración al hosting virtual
El terminal de cliente MetaTrader es ideal para realizar la automatización de las estrategias comerciales. Los desarrolladores de robots comerciales encuentran todo lo necesario: un potente lenguaje de programación MQL4/MQL5 en base a C++, el cómodo entorno de desarrollo MetaEditor, el simulador de estrategias multitipo con soporte para los cálculos distributivos MQL5 Cloud Network. En este artículo podrá conocer cómo trasladar su terminal de cliente con todos los desarrollos a un entorno virtual.
Desarrollo de una Startup social tecnológica, Parte I: Publicamos las Señales de MetaTrader 5 en el Twitter Desarrollo de una Startup social tecnológica, Parte I: Publicamos las Señales de MetaTrader 5 en el Twitter
Hoy vamos a hablar sobre cómo podemos vincular el terminal MetaTrader 5 con una cuenta del Twitter para publicar las señales de su Asesor Experto. Estamos desarrollando el Sistema social del soporte para la toma de decisiones (SDSS por sus siglas en inglés Social Decision Support System, denominado en adelante como SDSS) con PHP a base del servicio web RESTful. Esta idea se basa en la concepción del trading automático, o así denominado el trading mediante los ordenadores. Queremos que las señales comerciales automáticas del Asesor Experto (EA) pasen por los filtros de las facultades cognitivas de la mente humana.
Los bosques aleatorios predicen las tendencias Los bosques aleatorios predicen las tendencias
En el artículo se describe el uso del paquete Rattle para la búsqueda automática de patrones capaces de predecir "longs" y "shorts" para las parejas de divisas del mercado Fórex. El artículo será de utilidad tanto a los principiantes, como a los traders experimentados.
Recetas MQL5 - procesamiento del evento TradeTransaction Recetas MQL5 - procesamiento del evento TradeTransaction
En el artículo se describen las posibilidades del lenguaje MQL5 desde el punto de vista de la programación dirigida por eventos. La ventaja de este enfoque consiste en que el programa puede obtener información sobre la ejecución por etapas de la operación comercial. Se presenta un ejemplo de cómo con la ayuda del procesador del evento TradeTransaction se puede obtener y procesar la información sobre las acciones comerciales realizadas. Pienso que este enfoque se puede aplicar con toda tranquilidad para copiar las operaciones comerciales desde un terminal a otro.