
构建新兴的社交技术, 第二部分: 编制 MQL5 的 REST 客户端
介绍
在本文的 之前部分, 我们提出了一种所谓的社交决策支持系统的体系结构。一方面,该系统包括一个 MetaTrader 5 客户端,用来发送 EA 的自动决策至服务器端。在通信的另一端,有一个建立在瘦 PHP 框架的 Twitter(推特)应用程序,用于接收交易信号,并将它们存储到一个 MySQL 数据库,最后发布至有关人士。该 SDSS 的主要目的是记录可由人工执行的自动交易信号,并由人工做出相应的决策。这是可能的,因为自动交易信号可以通过这种方式展示给大量的专业观众。
在此第二部分我们打算采用 MQL5 编程语言开发 SDSS 的客户端。我们也会讨论一些替代方法,辨别它们的优点以及缺点。最后, 我们将把拼图的所有碎片集合到一起, 并完成塑造 PHP REST API 用于接收来自 EA 的交易信号。要做到这一点,我们必须研究某些涉及客户端编程的方面。
现在您可以将您的 MQL5 交易信号发布了!
1. 该 SDSS 的客户端
1.1. 在 OnTimer 事件中发布交易信号
我已经研究过如何显示来自 OnTimer 事件发送的交易信号的简单问题。在看过这个简单例子如何工作之后, 很容易推断其核心行为。
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)); } }
如您所见, 这个客户端应用的中心部分是 MQL5 的新函数 WebRequest。
编制一个自定义 MQL5 控件来处理 HTTP 通信是一个替代方案, 不过将这个任务委派给 MetaQuotes 的 新语言特性 将会更安全。
上述 MQL5 程序的输出如下:
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."}}
请注意, 服务器的响应消息:
{"status": "ok", "message": {"text": "Please wait until the time window has elapsed."}}
这是因为在 API 方法中实现了一个小型安全机制,用以防止高频剥头皮自动交易访问 SDSS :
/** * 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."}}'; } });
在 web 应用之中,以上简单机制会在 web 服务器检查入访的 HTTP 请求无恶意之后调用 (例如,入访信号非是拒绝服务攻击)。
Web 服务器负责防止此类攻击。例如, Apache 可以通过回避和安全模块组合防止它们。
一个典型的 Apache 模块 mod_evasive 配置,服务器管理员可以控制应用每秒钟接收 HTTP 请求的次数,等等。
<IfModule mod_evasive20.c> DOSHashTableSize 3097 DOSPageCount 2 DOSSiteCount 50 DOSPageInterval 1 DOSSiteInterval 1 DOSBlockingPeriod 60 DOSEmailNotify someone@somewhere.com </IfModule>
所以,如我们所说,该 PHP canTweet 方法的目的是阻止高频剥头皮,即把它看作对于 SDSS 的 HTTP 攻击。该 canTweet 方法在 Twetterer 类中实现 (将会稍后讨论):
/** * 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.'); } }
现在让我们来看看 WebRequest 自动为我们构建的 HTTP 请求的头部字段:
Content-Type: application/x-www-form-urlencoded Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
WebRequest 的 POST 方法假设程序员打算发送 HTML 表单数据,但在这种情况下,我们希望向服务器发送如下 HTTP 请求头部字段:
Content-Type: application/json Accept: application/json
没有什么灵丹妙药,我们必须考虑我们的决定,深入学习如何令 WebRequest 适应我们的需求,并发现利弊。
从技术角度来看,建立真正的 HTTP REST 会话更正确,但如我们所说,更安全的方案是通过 MetaQuotes 来委派 HTTP 会话,尽管 WebRequest() 的本意看起来像是为了访问网页,而非 web 服务。正是出于这个原因,我们最终将客户的交易信号进行 URL 编码。该 API 将接收以 URL 编码的信号,并将它们转换至 PHP 的 stdClass 格式。
一种替代 WebRequest() 函数的方案是使用 wininet.dll 库,编写工作于接近操作系统级别的自定义 MQL5 控件。文章 Using WinInet.dll for Data Exchange between Terminals via the Internet(使用 WinInet.dll 通过因特网在终端间进行数据交换) 和 Using WinInet in MQL5(在 MQL5 中使用 WinInet)。第二部分: POST 请求和文件 解释了这种方法的基本原理。然而,MQL5 开发者和 MQL5 社区的经验表明,这种解决方案并不像第一眼看上去那么简单。它表现出的缺点就是调用 WinINet 函数也许会由于 MetaTrader 的升级而遭到破坏。
1.2. 发布一款 EA 的交易信号
现在让我们来推断我们最近的解释。我已经创建了如下虚拟自动交易,以便说明有关控制剥头皮和拒绝服务攻击问题。
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); }
以上代码不能再简化了。这个 EA 在每次即时报价时放置单一的空头仓位。出于这种原因, 这个自动交易十分类似在短间隔时间内放置许多仓位, 尤其是在频繁振荡中您运行了它一段时间之后。对此无需担心。服务器端可以用两种方式控制发布间隔,一种是通过 web 服务器配置来防止 DoS 攻击, 还可以在 PHP 应用中定义确定的时间窗口, 如前解释的那样。
一切理清之后,您现在可以把发布功能添加进您喜爱的 EA 当中。
1.3. 如何让用户看到他们的交易信号?
在以下例子中, @laplacianlab 给与 SDSS 权限来发布之前章节贴出的 dummy EA 的信号:
图例 1. @laplacianlab 已经给与 SDSS 权限来发布他的代表
顺便说一句,布林带的名字出现在这个例子中,因为这是在本文的第一部分我们存储在 MySQL 数据库中的数据之一。id_ea=1 被关联到 "布林带", 但是我们将它修改为 "Dummy" 以便与解释搭配。在任何情况下,这是一个次要方面,但是很抱歉这有一点不便。
该 MySQL 数据库最终如下:
# 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. 该 SDSS 的服务器端
在继续塑造我们的社交决策支持系统服务器端之前,让我们简要地记得,此时我们有以下目录结构:
图例 2. 基于瘦 PHP API 的目录结构
2.1. PHP API 代码
根据我们的解释,该 index.php 文件将看起来像这样:
<?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. MySQL 面向对象编程封装器
我们现在必须在瘦应用的模块目录中创建 PHP 类 Tweeterer.php 和 EA.php。请注意,我们只是在一个简单的面向对象的类中封装一个 MySQL 数据表,而不必开发一个实际的模块层。
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; } } }
结论
我们已经开发出了本文第一部分介绍的 SDSS 客户端,以及根据决定塑造完成了服务器端。我们最终使用了新的 MQL5 的本地化函数 WebRequest()。有关规范方案的利弊, 我们已经看到 WebRequest() 原本不是作为 web 服务使用的, 只是构建访问网页的 GET 和 POST 请求。不过,与此同时,我们决定使用这项新功能,因为它比从头开发一个自定义控件更安全。
原本更优雅的做法是在 MQL5 客户端和 PHP 服务器之间建立真实的 REST 会话,但 WebRequest() 更适宜我们的具体需求。所以, 该 web 服务接收 URL-编码数据, 并将之转换到 PHP 可管理的格式。
我当下正为此系统工作。现在,我可以发布自己的个人交易信号了。这些功能, 可以为单用户工作, 但缺少了一些能在真实生产环境中完成复杂工作的部分。例如,瘦框架与数据库无关,所以你应该关心 SQL 注入。我们也没有解释如何保证 MetaTrader 5 终端和 PHP 应用程序之间的通信安全,所以请不要在实际环境中运行此应用程序,因为它只是作为本文中的介绍。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/1044
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.




新文章 构建新兴的社交技术, 第二部分: 编制 MQL5 的 REST 客户端已发布:
作者:Jordi Bassagan