English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Costruire una startup di tecnologia sociale, parte II: Programmazione di un client REST MQL5

Costruire una startup di tecnologia sociale, parte II: Programmazione di un client REST MQL5

MetaTrader 5Trading | 12 gennaio 2022, 11:31
213 0
laplacianlab
[Eliminato]

Introduzione

Nella parte precedente di questo articolo, abbiamo presentato l'architettura di un cosiddetto Social Decision Support System. Da un lato, questo sistema è costituito da un terminale MetaTrader 5 che invia le decisioni automatiche degli Expert Advisor al lato server. Dall'altro lato della comunicazione, c'è un'applicazione Twitter costruita sul framework Slim PHP che riceve quei segnali di trading, li memorizza in un database MySQL e infine li twitta alle persone. L'obiettivo principale dell'SDSS è registrare le azioni umane eseguite su segnali robotici e prendere decisioni umane di conseguenza. Ciò è possibile perché i segnali robotici possono essere esposti in questo modo a un pubblico molto ampio di esperti.

In questa seconda parte andremo a sviluppare il lato client di SDSS con il linguaggio di programmazione MQL5. Stiamo discutendo alcune alternative e identificando i pro ei contro di ciascuna di esse. Successivamente, metteremo insieme tutti i pezzi del puzzle e finiremo per modellare l'API PHP REST che riceve segnali di trading da Expert Advisor. Per fare questo dobbiamo prendere in considerazione alcuni aspetti coinvolti nella programmazione lato client.

Ora puoi twittare i tuoi segnali di trading MQL5!

Ora puoi twittare i tuoi segnali di trading MQL5!


1. Il lato client di SDSS

1.1. Tweeting di alcuni segnali di trading nell'evento OnTimer

Ho considerato di mostrare come vengono inviati i segnali di trading dall'evento OnTimer per questioni di semplicità. Dopo aver visto come funziona questo semplice esempio, sarà molto facile estrapolare questo comportamento di base a un normale Expert Advisor.

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));
     }         
  }

Come puoi vedere, la parte centrale di questa applicazione client è la nuova funzione WebRequest di MQL5.

La programmazione di un componente MQL5 personalizzato per gestire la comunicazione HTTP sarebbe un'alternativa a questa soluzione, tuttavia delegare questo compito a MetaQuotes tramite questa nuova funzionalità del linguaggio, è una scelta più sicura.

Il programma MQL5 sopra restituisce quanto segue:

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."}}

Si prega di notare che il server risponde con questo messaggio:

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

Questo perché c'è un piccolo meccanismo di sicurezza implementato nel metodo API signal/add per impedire all'SDSS di robot scalper iperattivi:

/**
 * 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."}}';
    }   
});

Il semplice meccanismo di cui sopra entra in gioco all'interno dell'app Web, subito dopo che il server Web ha già verificato che la richiesta HTTP in arrivo non sia dannosa (ad esempio, il segnale in arrivo non è un attacco denial of service).

Il server web può essere responsabile della prevenzione di tali attacchi. Ad esempio, Apache può prevenirli combinando i moduli evasive e security.

Questa è una tipica configurazione mod_evasive di Apache in cui l'amministratore del server può controllare le richieste HTTP che l'app può accettare al secondo, ecc.

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

Quindi, come diciamo, l'obiettivo del metodo PHP canTweet è bloccare gli scalper iperattivi che non sono considerati attacchi HTTP dall'SDSS. Il metodo canTweet è implementato nella classe Twetterer (di cui parleremo più avanti):

/**
 * 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.');
    }
}

Vediamo ora alcuni campi di intestazione della richiesta HTTP che WebRequest crea automaticamente per noi:

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

Il POST di WebRequest presuppone che i programmatori vogliano inviare alcuni dati del modulo HTML, tuttavia in questo scenario vorremmo inviare al server i seguenti campi di intestazione della richiesta HTTP:

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

Poiché non ci sono proiettili d'argento, dobbiamo essere coerenti con la nostra decisione e studiare a fondo come WebRequest si adatta alle nostre esigenze per scoprire i pro ei contro.

Sarebbe più corretto da un punto di vista tecnico stabilire veri dialoghi REST HTTP, ma come abbiamo detto è una soluzione più sicura delegare dialoghi HTTP a MetaQuotes anche se WebRequest() sembra essere originariamente destinato alle pagine web, non al web Servizi. È per questo motivo che finiremo per codificare l'URL del segnale di trading del cliente. L'API riceverà segnali con codifica url e quindi li convertirà nel formato stdClass di PHP.

Un'alternativa all'utilizzo della funzione WebRequest() consiste nello scrivere un componente MQL5 personalizzato che funzioni a un livello vicino al sistema operativo utilizzando la libreria wininet.dll. Gli articoli Utilizzo di WinInet.dll per lo scambio di dati tra terminali tramite Internet e Utilizzo di WinInet in MQL5. Parte 2: Richieste e file POST spiegano i fondamenti di questo approccio. Tuttavia, l'esperienza degli sviluppatori MQL5 e della community MQL5 ha dimostrato che questa soluzione non è così semplice come potrebbe sembrare a prima vista. Presenta lo svantaggio che le chiamate alle funzioni WinINet potrebbero interrompersi quando MetaTrader viene aggiornato.

1.2. Twittare i segnali di trading di un EA

Ora estrapoliamo ciò che abbiamo spiegato di recente. Ho creato il seguente robot fittizio per illustrare il problema dello scalping controllato e degli attacchi denial of 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);
}

Il codice sopra non può essere più semplice. Questo Expert Advisor piazza una sola posizione corta su ogni tick. Per questo motivo è molto probabile che questo robot finisca per piazzare molte posizioni in un breve intervallo di tempo, soprattutto se lo si esegue in un momento in cui c'è molta volatilità. Non c'è motivo di preoccuparsi. Il lato server controlla l'intervallo di tweeting sia configurando il server web per prevenire attacchi DoS, sia definendo una certa finestra temporale nell'applicazione PHP, come spiegato.

Con tutto questo chiaro, ora puoi prendere la funzione Tweet di questo EA e inserirla nel tuo Expert Advisor preferito.

1.3. In che modo gli utenti vedono i loro segnali di trading twittati?

Nell'esempio seguente, @laplacianlab autorizza l'SDSS a twittare i segnali dell'EA fittizio che è stato pubblicato nella sezione precedente:

Figura 1. @laplacianlab autorizza l'SDSS a twittare per suo conto

Figura 1. @laplacianlab ha dato il permesso all'SDSS di twittare per suo conto

A proposito, il nome Bollinger Bands appare in questo esempio perché è quello che abbiamo memorizzato nel database MySQL nella prima parte di questo articolo. id_ea=1 era associato a "Bollinger Bands", ma avremmo dovuto cambiarlo in qualcosa come "Dummy" per adattarsi bene a questa spiegazione. In ogni caso questo è un aspetto secondario ma scusate questo piccolo inconveniente.

Il database MySQL è infine il seguente:

# 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. Il lato server di SDSS

Prima di continuare a modellare il lato server del nostro Social Decision Support System, ricordiamo brevemente che al momento abbiamo la seguente struttura di directory:

Figura 2. Struttura della directory dell'API PHP basata su Slim

Figura 2. Struttura di directory dell'API PHP basata su Slim

2.1. Codice API PHP

Secondo quanto è stato spiegato il file index.php dovrebbe ora assomigliare a questo:

<?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. Wrapper OOP MySQL

Ora dobbiamo creare le classi PHP Tweeterer.php e EA.php nella directory del modello dell'applicazione Slim. Per favore, nota che, piuttosto che sviluppare un vero livello di modello, ciò che facciamo è avvolgere le tabelle MySQL in semplici classi orientate agli oggetti.

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;
        }
    } 
}

Conclusione

Abbiamo sviluppato il lato client dell'SDSS che è stato introdotto nella prima parte di questo articolo e abbiamo finito per modellare il lato server in base a questa decisione. Abbiamo finalmente utilizzato la nuova funzione nativa di MQL5 WebRequest(). Per quanto riguarda i pro ei contro di questa soluzione specifica, abbiamo visto che WebRequest() non è originariamente destinato a consumare servizi Web, ma a effettuare richieste GET e POST alle pagine Web. Tuttavia, allo stesso tempo, abbiamo deciso di utilizzare questa nuova funzionalità perché è più sicura rispetto allo sviluppo di un componente personalizzato da zero.

Sarebbe stato più elegante stabilire dialoghi veramente REST tra il client MQL5 e il server PHP, ma è stato molto più semplice adattare WebRequest() alla nostra specifica esigenza. Pertanto, il servizio Web riceve i dati codificati nell'URL e li converte in un formato gestibile per PHP.

Attualmente sto lavorando su questo sistema. Per ora posso twittare i miei segnali di trading personali. È funzionale, funziona per un singolo utente, ma mancano alcuni pezzi per farlo funzionare completamente in un ambiente di produzione reale. Ad esempio, Slim è un framework indipendente dal database, quindi dovresti preoccuparti delle iniezioni SQL. Né abbiamo spiegato come proteggere la comunicazione tra il terminale MetaTrader 5 e l'applicazione PHP, quindi per favore non eseguire questa app in un ambiente reale come viene presentato in questo articolo.

Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/1044

File allegati |
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)
Johnpaul77, Fornitori di segnali: "La nostra strategia rimane redditizia da più di tre anni. Allora perché dovremmo cambiarla?" Johnpaul77, Fornitori di segnali: "La nostra strategia rimane redditizia da più di tre anni. Allora perché dovremmo cambiarla?"
Sveliamo un piccolo segreto: I visitatori del sito web MQL5.com trascorrono la maggior parte del loro tempo sulla pagina del segnale Johnpaul77. È un leader del nostro rating del segnale con circa 900 abbonati con i fondi totali di $ 5,7 milioni su conti reali. Abbiamo intervistato i fornitori del segnale. Come si è scoperto, ce ne sono quattro! Come vengono distribuiti i compiti tra i membri del team? Quali strumenti tecnici usano? Perché si chiamano John Paul? E infine, in che modo dei gamer indonesiani qualunque sono diventati fornitori del miglior segnale su MQL5.com? Scopri tutto questo nell'articolo.
MQL5.com Freelance: Fonte di reddito degli sviluppatori (infografica) MQL5.com Freelance: Fonte di reddito degli sviluppatori (infografica)
In occasione del quarto compleanno del Servizio Freelance MQL5, abbiamo preparato un'infografica che dimostra i risultati del servizio dall’inizio della sua esistenza. Le cifre parlano da sole: fino ad oggi sono stati eseguiti più di 10.000 ordini per un valore totale di circa 600.000 dollari, mentre 3.000 clienti e 300 sviluppatori hanno utilizzato il servizio.
Ottimizzazione. Alcune semplici idee Ottimizzazione. Alcune semplici idee
Il processo di ottimizzazione può richiedere risorse significative del computer o anche degli agenti di test MQL5 Cloud Network. Questo articolo comprende alcune semplici idee che uso per facilitare il lavoro e migliorare il MetaTrader 5 Strategy Tester. Ho preso queste idee dalla documentazione, dal forum e dagli articoli.
Lavori freelance su MQL5.com - Il posto preferito dagli sviluppatori Lavori freelance su MQL5.com - Il posto preferito dagli sviluppatori
Gli sviluppatori di robot di trading non hanno più bisogno di pubblicizzare i loro servizi ai trader che richiedono Expert Advisor - ora i trader troveranno loro. Già, migliaia di trader effettuano ordini a sviluppatori freelance MQL5 e pagano per il lavoro su MQL5.com. Per 4 anni, questo servizio ha facilitato tremila commercianti a pagare più di 10 000 lavori svolti. E l'attività di trader e sviluppatori è in costante crescita!