Costruire una startup di tecnologia sociale, parte II: Programmazione di un client REST MQL5
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!
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 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 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
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso