English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Construire une start-up de technologie sociale, Deuxième partie : Programmation d'un client REST MQL5

Construire une start-up de technologie sociale, Deuxième partie : Programmation d'un client REST MQL5

MetaTrader 5Trading | 13 janvier 2022, 15:39
273 0
laplacianlab
[Supprimé]

Introduction

Dans la partie précédente de cet article, nous avons présenté l'architecture d'un soi-disant système d'aide à la décision sociale. D'une part, ce système se compose d'un terminal MetaTrader 5 envoyant les décisions automatiques des Expert Advisors vers le serveur. De l'autre côté de la communication, il y a une application Twitter construite sur le cadre du Slim PHP qui reçoit ces signaux de trading, les stocke dans une base de données MySQL, et enfin les tweeter vers les gens. L'objectif principal du SDSS est d'enregistrer les actions humaines effectuées sur des signaux robotiques et de prendre des décisions humaines en conséquence. Cela est possible car les signaux robotiques peuvent être ainsi exposés à un très large public d'experts.

Dans cette deuxième partie, nous allons développer le côté client du SDSS avec le langage de programmation MQL5. Nous discutons de certaines alternatives et identifions les avantages et les inconvénients de chacune d'entre elles. Ensuite, nous assemblerons toutes les pièces du puzzle et finirons par façonner l'API PHP REST qui reçoit les signaux de trading des Expert Advisors. Pour ce faire, nous devons prendre en compte certains aspects impliqués dans la programmation côté client.

Vous pouvez maintenant tweeter vos signaux de trading MQL5 !

Vous pouvez maintenant tweeter vos signaux de trading MQL5 !


1. Le côté client du SDSS

1.1. Tweeter certains signaux de trading dans l’évènement OnTimer

J'ai envisagé de montrer comment les signaux de trading sont envoyés à partir de l'événement OnTimer pour des problèmes de simplicité. Après avoir vu comment fonctionne cet exemple simple, il sera très facile d'extrapoler ce comportement de base vers un Expert Advisor ordinaire.

dummy_ontimer.mq5 :

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

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

Comme vous pouvez le voir, la partie centrale de cette application cliente est la nouvelle fonction WebRequest de MQL5.

Programmer un composant MQL5 personnalisé pour gérer la communication HTTP serait une alternative à cette solution, mais déléguer cette tâche à MetaQuotes via cette nouvelle fonctionnalité de langue est plus sûre.

Le programme MQL5 ci-dessus génère les éléments suivants :

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

Veuillez noter que le serveur répond avec ce message :

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

En effet, il existe un petit mécanisme de sécurité implémenté dans le signal / l’ajout de la méthode API pour empêcher le SDSS des robots scalpeurs hyperactifs :

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

Le mécanisme simple ci-dessus entre en jeu dans l'application Web, juste après que le serveur Web ait déjà vérifié que la requête HTTP entrante n'est pas malveillante (par exemple, le signal entrant n'est pas une attaque par déni de service).

Le serveur Web peut être responsable de la prévention de telles attaques. À titre d'exemple, Apache peut les empêcher en combinant les modules evasive et security.

Il s'agit d'une configuration mod_evasive typique d'Apache où l'administrateur du serveur peut contrôler les requêtes HTTP que l'application peut accepter par seconde, etc.

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

Donc, comme on le dit, le but de la méthode PHP canTweet est de bloquer les scalpeurs hyperactifs qui ne sont pas considérés comme des attaques HTTP par le SDSS. La méthode canTweet est implémentée dans la classe Twetterer (qui sera discutée plus loin) :

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

Voyons maintenant quelques champs d'en-tête de requête HTTP que WebRequest crée automatiquement pour nous :

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

Le POST de WebRequest suppose que les programmeurs souhaitent envoyer des données de formulaire HTML, néanmoins dans ce scénario, nous voudrions envoyer au serveur les champs d'en-tête de requête HTTP suivants :

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

Comme il n'y a pas de solution miracle, nous devons être cohérents avec notre décision et étudier en profondeur comment WebRequest répond à nos exigences afin de découvrir les avantages et les inconvénients.

Il serait plus correct d'un point de vue technique d'établir de véritables dialogues HTTP REST, mais comme nous l'avons dit, c'est une solution plus sûre de déléguer des dialogues HTTP à MetaQuotes même si WebRequest() semble être à l'origine destiné aux pages web, et non aux services web. C'est pour cette raison que nous finirons par encoder l'url du signal de trading du client. L'API recevra les signaux codés par l’url, puis les convertira au format stdClass de PHP.

Une alternative à l'utilisation de la fonction WebRequest() consiste à écrire un composant MQL5 personnalisé fonctionnant à un niveau proche du système d'exploitation à l'aide de la bibliothèque wininet.dll. Les articles Utilisation de WinInet.dll pour l'échange de données entre terminaux via Internet et Utilisation de WinInet dans MQL5. Deuxième partie : Les requêtes et fichiers POST expliquent les principes fondamentaux de cette approche. Cependant, l'expérience des développeurs MQL5 et de la communauté MQL5 a montré que cette solution n'est pas aussi simple qu'elle paraît à première vue. Cela présente l'inconvénient que les appels aux fonctions WinINet peuvent être interrompus lors de la mise à niveau de MetaTrader.

1.2. Tweeter les signaux de trading d'un EA

Maintenant, extrapolons ce que nous avons récemment expliqué. J'ai créé le robot factice suivant afin d'illustrer le problème du scalping contrôlé et des attaques par déni de service.

Dummy.mq5 :

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

Le code ci-dessus ne peut pas être plus simple. Cet Expert Advisor ne place qu'une seule position courte sur chaque coche. Pour cette raison, il est très probable que ce robot finisse par placer de nombreuses positions dans un court intervalle de temps, surtout si vous l'exécutez à un moment où il y a beaucoup de volatilité. Il n'y a aucune raison de s'inquiéter. Le côté serveur contrôle l'intervalle de tweet en configurant le serveur web pour empêcher les attaques DoS et en définissant une certaine fenêtre de temps dans l'application PHP, comme expliqué.

Tout cela étant clair, vous pouvez maintenant utiliser la fonction Tweet de cet EA et la placer dans votre Expert Advisor préféré.

1.3. Comment les utilisateurs voient-ils leurs signaux de trading tweetés ?

Dans l'exemple suivant, @laplacianlab autorise le SDSS à tweeter les signaux de l'EA factice qui ont été publiés dans la section précédente :

Figure 1. @laplacianlab autorise le SDSS à tweeter en son nom

Figure 1. @laplacianlab a autorisé le SDSS à tweeter en son nom

D’ailleurs, le nom Bollinger Bands apparaît dans cet exemple car c'est celui que nous avons stocké dans la base de données MySQL dans la première partie de cet article. id_ea=1 était associé à "Bollinger Bands", mais nous aurions dû le changer en quelque chose comme "Dummy" afin de bien correspondre à cette explication. En tout cas c'est un aspect secondaire mais désolé pour ce petit désagrément.

La base de données MySQL est finalement la suivante :

# MySQL database creation...

CREATE DATABASE IF NOT EXISTS laplacianlab_com_sdss;

use laplacianlab_com_sdss;

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

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

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

# Dump some sample data...

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

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


2. Le côté serveur du SDSS

Avant de continuer à façonner le côté serveur de notre système d'aide à la décision sociale, rappelons-nous brièvement que nous avons actuellement la structure de répertoire suivante :

Figure 2. Structure de répertoire de l'API PHP basée sur Slim

Figure 2. Structure de répertoire de l'API PHP basée sur Slim

2.1. Code API PHP

D'après ce qui a été expliqué, le fichier index.php devrait maintenant ressembler à ceci :

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

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

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

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

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

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

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

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

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

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

2.2. Wrappers de POO MySQL

Il faut maintenant créer les classes PHP Tweeterer.php et EA.php dans le répertoire model de l'application Slim. Veuillez noter qu'au lieu de développer une couche de modèle réelle, nous enveloppons les tables MySQL dans de simples classes orientées par objet.

model\Tweeterer.php :

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

model\EA.php :

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

model\DBConnection.php :

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

Conclusion

Nous avons développé le côté client du SDSS qui a été présenté dans la première partie de cet article, et avons fini par façonner le côté serveur en fonction de cette décision. Nous avons enfin utilisé la nouvelle fonction native de MQL5 WebRequest(). Concernant les avantages et les inconvénients de cette solution spécifique, nous avons vu que WebRequest() n'est pas destiné à l'origine à consommer des services web, mais à effectuer des requêtes GET et POST vers des pages web. Cependant, dans le même temps, nous avons décidé d'utiliser cette nouvelle fonctionnalité car elle est plus sûre que de développer un composant personnalisé à partir de zéro.

Il aurait été plus élégant d'établir de véritables dialogues REST entre le client MQL5 et le serveur PHP, mais il a été beaucoup plus simple d'adapter WebRequest() à notre besoin spécifique. Ainsi, le service web reçoit des données codées en URL et les convertit dans un format gérable pour PHP.

Je travaille actuellement sur ce système. Pour l'instant, je peux tweeter mes signaux de trading personnels. Il est opérationnel, il fonctionne pour un seul utilisateur, mais il manque quelques pièces pour qu'il fonctionne complètement dans un environnement de production réel. Par exemple, Slim est une infrastructure logicielle indépendante de la base de données, vous devez donc vous soucier des injections SQL. Nous n'avons pas non plus expliqué comment sécuriser la communication entre le terminal MetaTrader 5 et l'application PHP, veuillez donc ne pas exécuter cette application dans un environnement réel tel qu'il est présenté dans cet article.

Traduit de l’anglais par MetaQuotes Ltd.
Article original : https://www.mql5.com/en/articles/1044

Fichiers joints |
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)
Fournisseurs de signaux Johnpaul77 : « Notre stratégie demeure rentable depuis plus de trois ans maintenant. Alors pourquoi la changerions-nous ? » Fournisseurs de signaux Johnpaul77 : « Notre stratégie demeure rentable depuis plus de trois ans maintenant. Alors pourquoi la changerions-nous ? »
Laissez-nous vous révéler un petit secret : Les visiteurs du site Web MQL5.com passent la plupart de leur temps sur la page du signal Johnpaul77. C'est un leader de notre évaluation de signal avec environ 900 abonnés et des fonds totaux de 5,7 millions de dollars sur des comptes réels. Nous avons interrogé les fournisseurs de signaux. Il s'est avéré qu'ils sont quatre ! Comment les tâches sont-elles réparties entre les membres de l'équipe ? Quels outils techniques utilisent-ils ? Pourquoi les appelle-t-on John Paul ? Et enfin, comment les joueurs ordinaires d'Indonésie sont-ils devenus des fournisseurs du meilleur signal sur MQL5.com ? Découvrez tout cela dans l'article.
MQL5.com Freelance : Source de revenus des développeurs (infographie) MQL5.com Freelance : Source de revenus des développeurs (infographie)
À l’occasion du quatrième anniversaire de MQL5 Freelance Service, nous avons préparé une infographie démontrant les résultats du service pour toute la durée de son existence. Les chiffres parlent d’eux-mêmes : plus de 10 000 commandes d’une valeur totale d’environ 600 000 dollars ont été exécutées à ce jour, tandis que 3 000 clients et 300 développeurs ont déjà utilisé le service.
Optimisation. Quelques idées simples Optimisation. Quelques idées simples
Le processus d'optimisation peut nécessiter des ressources importantes de votre ordinateur ou même des agents de test de MQL5 Cloud Network. Cet article comprend quelques idées simples que j'utilise pour faciliter le travail et améliorer le testeur de stratégie de MetaTrader 5. J'ai eu ces idées dans la documentation, le forum et les articles.
Emplois indépendants sur MQL5.com - L'endroit préféré des développeurs Emplois indépendants sur MQL5.com - L'endroit préféré des développeurs
Les développeurs de robots de trading n'ont plus besoin de commercialiser leurs services auprès des traders ayant besoin d'Expert Advisors, car ces derniers vous trouveront désormais. Déjà, des milliers de traders passent des ordres aux développeurs indépendants de MQL5 et paient le travail effectué sur MQL5.com. Pendant 4 ans, ce service a permis à trois mille traders de rémunérer plus de 10 000 travaux réalisés. Et l'activité des traders et des développeurs ne cesse de croître !