构建新兴的社交技术, 第二部分: 编制 MQL5 的 REST 客户端·综合运用
📘

构建新兴的社交技术, 第二部分: 编制 MQL5 的 REST 客户端·综合运用

第 3/3 篇

「信号推送的时间窗闸门」

做信号入库前,先得卡住推送节奏,不然同一窗口内反复写信号只会污染数据库。下面这段分支就是按时间窗类型判断距离上次推送的间隔 $diff 是否超限:1 分钟窗看 60 秒、1 小时窗看 3600 秒、1 天窗看 86400 秒,没超就禁发,超了才放行。 放行后直接 return true 让调用链继续;被拦下则抛异常提示等时间窗走完。这种写法把节奏控制埋在类方法里,EA 侧不用重复判断,适合多账号跟推的场景。 紧跟着的 addSignal 负责落库:把 id_ea、id_twitterer、symbol、operation、value 拼成 INSERT 语句写进 signals 表,再返回新行 id。注意这里用的是字符串拼接而非预处理,实盘接外部数据时有注入隐患,自己改的时候建议加转义。 exists 方法开头接着做账号校验,确认 twitterer 存在才允许后续写入,避免脏 id 进表。

MQL5 / C++
class="kw">switch($timeWindow)
{
	case &class="macro">#x27;class="num">1 minute&class="macro">#x27;:
		$diff <= class="num">60 ? $canTweet = false : $canTweet = true;
		class="kw">break;
	case &class="macro">#x27;class="num">1 hour&class="macro">#x27;:
		$diff <= class="num">3600 ? $canTweet = false : $canTweet = true;
		class="kw">break;
	case &class="macro">#x27;class="num">1 day&class="macro">#x27;:
		$diff <= class="num">86400 ? $canTweet = false : $canTweet = true;
		class="kw">break;
	class="kw">default:
		$canTweet = false;
		class="kw">break;
}
if($canTweet)
{
	class="kw">return true;
}
else
{
	throw new Exception(&class="macro">#x27;Please wait until the time window has elapsed.&class="macro">#x27;);
}
}
class=class="str">"cmt">/**
 * Adds a new signal
 * @param type $id_twitterer
 * @param stdClass $data
 * @class="kw">return integer The new row id
 */
class="kw">public function addSignal($id_twitterer, stdClass $data)
{
	$sql = &class="macro">#x27;INSERT INTO signals(id_ea, id_twitterer, symbol, operation, value) VALUES(&class="macro">#x27;
		. $data->id_ea . ","
		. $id_twitterer . ",&class="macro">#x27;"
		. $data->symbol . "&class="macro">#x27;,&class="macro">#x27;"
		. $data->operation . "&class="macro">#x27;,"
		. $data->value . &class="macro">#x27;)&class="macro">#x27;;
	DBConnection::getInstance()->query($sql);
	class="kw">return DBConnection::getInstance()->getHandler()->insert_id;
}
class=class="str">"cmt">/**
 * Checks whether the given twitterer exists
 * @param class="type">class="kw">string $id
 * @class="kw">return boolean
 */
class="kw">public function exists($id)
{

◍ 信号源与EA的数据库读写封装

把推特账号和EA的配置落库,核心就是几个轻量PHP方法:用 twitter_id 查重、插入新记录拿自增id、按 id 更新 token,以及按倒序取最近一条信号。getLastSignal 里 LIMIT 1 配 ORDER BY id DESC,保证每轮轮询只比对最新信号,避免重复下单。 下面这段是 Twitterer 类的实际写法,注意它直接拼 SQL 字符串,没有预处理——在私有闭环脚本里能用,但挂公网就有注入风险。 $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; } } } EA 类更薄,只有一个 get($id) 按主键直查 eas 表,返回对象供后续反序列化参数用。 require_once 'DBConnection.php'; /**

  • EA's simple OOP wrapper

*

  • @author Jordi Bassagañas
  • @copyright 2014 Jordi Bassagañas
  • @link [MQL5官方文档]

*/ class EA { 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(); } } 在 MT5 侧接这套接口时,外汇与贵金属波动剧烈、杠杆风险高,信号延迟几秒就可能让跟单偏移;建议本地缓存 last_signal 的 created_at,比对时间戳而非每次全量拉取。

MQL5 / C++
$sql = "SELECT * FROM $this->table WHERE twitter_id=&class="macro">#x27;$id&class="macro">#x27;";
$result = DBConnection::getInstance()->query($sql);
class="kw">return (boolean)$result->num_rows;
}
class=class="str">"cmt">/**
 * Creates a new twitterer
 * @param stdClass $data
 * @class="kw">return integer The new row id
 */
class="kw">public function create(stdClass $data)
{
$sql = "INSERT INTO $this->table(twitter_id, access_token, access_token_secret) "
. "VALUES(&class="macro">#x27;"
. $data->twitter_id . "&class="macro">#x27;,&class="macro">#x27;"
. $data->access_token . "&class="macro">#x27;,&class="macro">#x27;"
. $data->access_token_secret . "&class="macro">#x27;)";
DBConnection::getInstance()->query($sql);
class="kw">return DBConnection::getInstance()->getHandler()->insert_id;
}
class=class="str">"cmt">/**
 * Updates the twitterer&class="macro">#x27;s data
 * @param stdClass $data
 * @class="kw">return Mysqli object
 */
class="kw">public function update(stdClass $data)
{
$sql = "UPDATE $this->table SET "
. "access_token = &class="macro">#x27;" . $data->access_token . "&class="macro">#x27;, "
. "access_token_secret = &class="macro">#x27;" . $data->access_token_secret . "&class="macro">#x27; "
. "WHERE twitter_id =&class="macro">#x27;" . $data->twitter_id . "&class="macro">#x27;";
class="kw">return DBConnection::getInstance()->query($sql);
}
class=class="str">"cmt">/**
 * Gets the last trading signal sent by the twitterer
 * @param type $id The twitterer id
 * @class="kw">return mixed The last trading signal
 */
class="kw">public function getLastSignal($id)
{
$sql = "SELECT * FROM signals WHERE id_twitterer=$id ORDER BY id DESC LIMIT class="num">1";
$result = DBConnection::getInstance()->query($sql);
if($result->num_rows == class="num">1)
{
class="kw">return $result->fetch_object();
}
else
{
$signal = new stdClass;
$signal->created_at = null;
class="kw">return $signal;
}
}
}
<?php
require_once &class="macro">#x27;DBConnection.php&class="macro">#x27;;
class=class="str">"cmt">/**
* EA&class="macro">#x27;s simple OOP wrapper
*
* @author Jordi Bassagañas
* @copyright class="num">2014 Jordi Bassagañas
* @link [MQL5官方文档]
*/
class EA
{
class="kw">protected $table = &class="macro">#x27;eas&class="macro">#x27;;
class=class="str">"cmt">/**
 * Gets an EA by id
 * @param integer $id
 * @class="kw">return stdClass
 */
class="kw">public function get($id)
{
$sql = "SELECT * FROM $this->table WHERE id=$id";
class="kw">return DBConnection::getInstance()->query($sql)->fetch_object();
}
}

用单例封装 MT5 外的 MySQL 连接

把数据库访问收进一个类里,能避免 EA 多次初始化时反复建连。下面这段 PHP 风格的单例类(常见于 MT5 外接 Web 服务桥接层)只保留一个 DBConnection 实例,构造函数私有,外部无法直接 new。 getInstance() 用 instanceof 判断静态成员是否为自身实例,不是才新建,是则直接返回。这样在 1 分钟 K 线回调里反复取连接,底层 MySQL 握手次数倾向稳定在 1 次而非 N 次。 query() 对 false 显式抛异常而不是静默返回,配合 mysqli_report(MYSQLI_REPORT_STRICT) 能把连不上库的问题在日志里立刻暴露,而不是让策略在空数据上继续跑。外汇与贵金属信号外接库时延迟敏感,连不上就抛比假信号更可控,属高风险环节需本地先测。

MQL5 / C++
class=class="str">"cmt">/**
* DBConnection class
*
* @author      Jordi Bassagañas
* @copyright   class="num">2014 Jordi Bassagañas
* @link        [MQL5官方文档]
*/
class DBConnection
{
    class=class="str">"cmt">/**
     * @var DBConnection Singleton instance
     */
    class="kw">private class="kw">static $instance;
    class=class="str">"cmt">/**
     * @var mysqli Database handler
     */
    class="kw">private $mysqli;
    class=class="str">"cmt">/**
     *  Opens a new connection to the MySQL server
     */
    class="kw">private function __construct()
    {
        mysqli_report(MYSQLI_REPORT_STRICT);
        try {
            $this->mysqli = new MySQLI(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);
        } class="kw">catch (Exception $e) {            
            throw new Exception(&class="macro">#x27;Unable to connect to the database, please, try again later.&class="macro">#x27;);
        }
    }
    class=class="str">"cmt">/**
     * Gets the singleton instance
     * @class="kw">return type
     */
    class="kw">public class="kw">static function getInstance()
    {
        if (!self::$instance instanceof self) self::$instance = new self;
        class="kw">return self::$instance;
    }
    class=class="str">"cmt">/**
     * Gets the database handler
     * @class="kw">return mysqli
     */
    class="kw">public function getHandler()
    {
        class="kw">return $this->mysqli;
    }
    class=class="str">"cmt">/**
     * Runs the given query
     * @param class="type">class="kw">string $sql
     * @class="kw">return mixed
     */
    class="kw">public function query($sql)
    {
        $result = $this->mysqli->query($sql);
        if ($result === false)
        {       
            throw new Exception(&class="macro">#x27;Unable to run query, please, try again later.&class="macro">#x27;);
        }
        else
        {
            class="kw">return $result;
        }
    }
}

「把工具请下神坛」

这套 SDSS 客户端的落地,最终靠的是 MQL5 的 WebRequest() 而非完整 REST 会话。它本只是用来发 GET/POST 访问网页,拿来当轻量通信通道虽不优雅,但比自己造控件省心也更稳。 服务端收的是 URL 编码数据,再转成 PHP 能管的格式;瘦框架与数据库无关,SQL 注入得自己兜底。MetaTrader 5 终端和 PHP 应用之间的链路加密本文没碰,所以这套只能当 demo,别直接扔实盘环境跑。 眼下个人信号能发了,单用户跑得通,离生产级还差一截。工具是手段,真要上外汇或贵金属,高波动和断连风险都在,先开 MT5 把 dummy_ontimer.mq5 跑一遍看看心跳再说。

常见问题

在信号推送前加时间窗闸门,只让伦敦和纽约重叠时段(约北京时间20:00-24:00)的信号通过,其余时段静默。
用单例模式封装数据库连接器,EA 初始化时建一次实例,后续读写直接调同一连接,避免重复代码和断连。
可以。小布盯盘的 AIGC 能按你设的时间窗和品种波动阈值预筛信号,只在高概率时段把有效预警推到你眼前。
把读写封装收进同一个数据库工具类,字段映射集中定义,改结构只动一处,EA 和信号源都引用它。
没必要神化。先跑通你自己的信号闭环,等多人复用再抽通用库,过早抽象反而拖慢验证。