构建新兴的社交技术, 第一部分: 发布您的 MetaTrader 5 信号·进阶篇
📘

构建新兴的社交技术, 第一部分: 发布您的 MetaTrader 5 信号·进阶篇

第 2/3 篇

信号系统的库表怎么落

顺着 REST API 的字段往回看,这套信号服务后端其实就两张核心表:eas 存 EA 本体,signals 存具体喊单。两者用 id_ea 做外键关联,写信号的程序必须先有合法的 EA 记录才能落库。 建库语句里用了 InnoDB 引擎,mediumint UNSIGNED 给 eas 的 id 留了约 1677 万的上限,对多数私用信号池够用;signals 的 value 用 DECIMAL(9,5),意味着价格类字段最大到 999999.99999,外汇报价精度完全覆盖。 示例数据直接塞了 Bollinger Bands 与 Two EMA 两个 EA,描述里写明跑 H4 周期。你在本地 MySQL 跑下面这段,就能复刻出可对接 MT5 信号推送的最小库结构。 生产环境务必改掉 'password' 这个授权口令,原文也标了注释提醒。

MQL5 / C++
# MySQL database creation...
CREATE DATABASE IF NOT EXISTS sdss;
use sdss;
# Please, change the user&class="macro">#x27;s password in production
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES, 
CREATE TEMPORARY TABLES ON `sdss`.* TO &class="macro">#x27;laplacianlab&class="macro">#x27;@&class="macro">#x27;localhost&class="macro">#x27; IDENTIFIED BY &class="macro">#x27;password&class="macro">#x27;; 
CREATE TABLE IF NOT EXISTS eas(
   id mediumint UNSIGNED NOT NULL AUTO_INCREMENT, 
   name VARCHAR(class="num">32),
   description TEXT,
   created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY(id)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS signals(
   id class="type">int UNSIGNED NOT NULL AUTO_INCREMENT,
   id_ea mediumint UNSIGNED NOT NULL,
   symbol VARCHAR(class="num">10) NOT NULL,
   operation VARCHAR(class="num">6) NOT NULL,
   value DECIMAL(class="num">9,class="num">5) NOT NULL,
   created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY(id),
   FOREIGN KEY(id_ea) REFERENCES eas(id)
) ENGINE=InnoDB;
# Dump some sample data...
INSERT INTO eas(name, description) VALUES(&class="macro">#x27;Bollinger Bands&class="macro">#x27;, &class="macro">#x27;<p>Robot based on Bollinger Bands. Works with H4 charts.</p>&class="macro">#x27;),
(&class="macro">#x27;Two EMA&class="macro">#x27;, &class="macro">#x27;<p>Robot based on the crossing of two MA. Works with H4 charts.</p>&class="macro">#x27;);

◍ 用 Slim 把 MT5 信号写进本地 API

Slim 是个极轻量的 PHP 微框架,几行代码就能撑起一个 RESTful API。教学示例里我们只实现 HTTP POST 方法,路径定为 /signal/add,用来接收 EA 抛过来的信号并落库。 本地跑通前有两件脏活:在 C:\Windows\System32\Drivers\etc\hosts 里加一条 api.laplacianlab.local 的解析,再给 Apache 配一个对应的虚拟主机。缺了这两步,浏览器和 MT5 都找不到你的服务。 用 Firefox 的 RESTClient 插件发 POST 到 http://api.laplacianlab.local/signal/add,正常会回 HTTP 200 加 JSON 实体。下面这段是核心骨架,包含常量定义、单例数据库连接、重写规则和路由处理。 目录结构实测是 config/config.php、model/DBConnection.php、public/.htaccess、public/index.php、composer.json 这五块。生产环境务必加认证层,否则任何人都可能往你的 signals 表塞脏数据。

MQL5 / C++
<?php
class=class="str">"cmt">// Creating constants.
class=class="str">"cmt">// General
define(&class="macro">#x27;BASE_URL&class="macro">#x27;, &class="macro">#x27;laplacianlab.local&class="macro">#x27;);
define(&class="macro">#x27;APPLICATION_PATH&class="macro">#x27;, realpath(dirname(__FILE__)) . &class="macro">#x27;/../&class="macro">#x27;);
class=class="str">"cmt">// Database connection
define(&class="macro">#x27;DB_NAME&class="macro">#x27;, &class="macro">#x27;sdss&class="macro">#x27;);
define(&class="macro">#x27;DB_USER&class="macro">#x27;, &class="macro">#x27;laplacianlab&class="macro">#x27;);
define(&class="macro">#x27;DB_PASSWORD&class="macro">#x27;, &class="macro">#x27;password&class="macro">#x27;); class=class="str">"cmt">// Don&class="macro">#x27;t forget to change this in your production server!
define(&class="macro">#x27;DB_SERVER&class="macro">#x27;, &class="macro">#x27;localhost&class="macro">#x27;);
<?php
class DBConnection
{
    class="kw">private class="kw">static $instance;
    class="kw">private $mysqli;
    class="kw">private function __construct()
    {
        $this->mysqli = new MySQLI(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);
    }
    class="kw">public class="kw">static function getInstance()
    {
        if (!self::$instance instanceof self) self::$instance = new self;
        class="kw">return self::$instance;
    }
    class="kw">public function getHandler()
    {
        class="kw">return $this->mysqli;
    }
}
<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>
    RewriteEngine On
    # Redirect Trailing Slashes...
    RewriteRule ^(.*)/$ /$class="num">1 [L,R=class="num">301]
    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>
<?php
class=class="str">"cmt">// Bootstrap logic
require_once &class="macro">#x27;../config/config.php&class="macro">#x27;;
set_include_path(get_include_path() . PATH_SEPARATOR . APPLICATION_PATH . &class="macro">#x27;/vendor/&class="macro">#x27;);
set_include_path(get_include_path() . PATH_SEPARATOR . APPLICATION_PATH . &class="macro">#x27;/model/&class="macro">#x27;);
require_once &class="macro">#x27;slim/slim/Slim/Slim.php&class="macro">#x27;;
require_once &class="macro">#x27;DBConnection.php&class="macro">#x27;;
use \Slim\Slim;
Slim::registerAutoloader();
$app = new Slim();
$app->response->headers->set(&class="macro">#x27;Content-Type&class="macro">#x27;, &class="macro">#x27;application/json&class="macro">#x27;);
class=class="str">"cmt">// RESTful API methods
class=class="str">"cmt">// POST signal/add
$app->post(&class="macro">#x27;/signal/add&class="macro">#x27;, function() {  
    $request = Slim::getInstance()->request();      
    $signal = json_decode($request->getBody());
    $sql = &class="macro">#x27;INSERT INTO signals(id_ea, symbol, operation, value) VALUES(&class="macro">#x27;
        . mysql_real_escape_string($signal->ea_id) . ",&class="macro">#x27;"
        . mysql_real_escape_string($signal->symbol) . "&class="macro">#x27;,&class="macro">#x27;"
        . mysql_real_escape_string($signal->operation) . "&class="macro">#x27;,"
        . mysql_real_escape_string($signal->value) . &class="macro">#x27;)&class="macro">#x27;;
    DBConnection::getInstance()->getHandler()->query($sql);
    $signal->id = DBConnection::getInstance()->getHandler()->insert_id;
    echo json_encode($signal);          
});
class=class="str">"cmt">// More API methods here!.., according to your API spec
$app->run();
{
    "require": {
        "slim/slim": "class="num">2.*",

「EA 信号回传的本地接口与报文结构」

把 MQL5 写的 EA 接到自建分析中枢,第一步往往是让策略把信号推到本地虚拟主机。上面这段配置把 api.laplacianlab.local 绑到 127.0.0.1,并用 Apache 2.4.4(Win64)+ PHP 5.4.12 在 80 端口起了只接受本机访问的虚拟站点,目录权限里 Deny from all 之后单独 Allow from 127.0.0.1,外网扫不到。 HTTP 响应头里 Content-Length 为 70,Status Code 200 OK,Keep-Alive 设了 timeout=5, max=100,说明这条连接最多复用 100 次、空闲 5 秒就断。EA 侧拿到的 JSON 体很小:ea_id 为 1,symbol 是 AUDUSD,operation 为 BUY,value 为 0.9281,id 为 22。 你在 MT5 里验证时,可直接用 WebRequest 向 http://api.laplacianlab.local 发 POST,看能否收回同样字段。外汇与贵金属杠杆高、滑点大,这类本地回传只解决信号落地,不替代风控,实盘前务必在策略测试器跑够样本。

MQL5 / C++
::class="num">1 localhost
# Copyright(c) class="num">1993-class="num">2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments(such as these) may be inserted on individual
# lines or following the machine name denoted by a &class="macro">#x27;#&class="macro">#x27; symbol.
#
# For example:
#
#      class="num">102.54.class="num">94.97     rhino.acme.com          # source server
#      class="num">38.25.class="num">63.10     x.acme.com              # x client host
# localhost name resolution is handled within DNS itself.
#       class="num">127.0.class="num">0.1       localhost
#       ::class="num">1             localhost
class="num">127.0.class="num">0.1       localhost
class="num">127.0.class="num">0.1       api.laplacianlab.local
# Virtual Hosts
#
# Required modules: mod_log_config
# If you want to maintain multiple domains/hostnames on your
# machine you can setup VirtualHost containers for them. Most configurations
# use only name-based class="kw">virtual hosts so the server doesn&class="macro">#x27;t need to worry about
# IP addresses. This is indicated by the asterisks in the directives below.
#
# Please see the documentation at
# <URL:http:class=class="str">"cmt">//httpd.apache.org/docs/class="num">2.4/vhosts/>
# for further details before you try to setup class="kw">virtual hosts.
#
# You may use the command line option &class="macro">#x27;-S&class="macro">#x27; to verify your class="kw">virtual host
# configuration.
#
# VirtualHost example:
# Almost any Apache directive may go into a VirtualHost container.
# The first VirtualHost section is used for all requests that do not
# match a ServerName or ServerAlias in any <VirtualHost> block.
#
<VirtualHost *:class="num">80>
        ServerAdmin webmaster@laplacianlab.local
        DocumentRoot "c:/wamp/www/laplacianlab/class="kw">public"
        ServerName api.laplacianlab.local
        ErrorLog "logs/api.laplacianlab.local-error.log"
        CustomLog "logs/api.laplacianlab.local-access.log" common
        <directory "c:/wamp/www/laplacianlab/class="kw">public">
                Options FollowSymLinks
                AllowOverride all
                Order Deny,Allow
                Deny from all
                Allow from class="num">127.0.class="num">0.1
        </directory>
</VirtualHost>
Status Code: class="num">200 OK
Connection: Keep-Alive
Content-Length: class="num">70
Content-Type: application/json
Date: Mon, class="num">07 Apr class="num">2014 class="num">18:class="num">12:class="num">34 GMT
Keep-Alive: timeout=class="num">5, max=class="num">100
Server: Apache/class="num">2.4.class="num">4 (Win64) PHP/class="num">5.4.class="num">12
X-Powered-By: PHP/class="num">5.4.class="num">12
{
        "ea_id": class="num">1,
        "symbol": "AUDUSD",
        "operation": "BUY",
        "value": class="num">0.9281,
        "id": class="num">22
}

把交易信号推到 Twitter 的 OAuth 接法

想让推友授权你的 SDSS 代发交易信号,先去 Twitter 开发者后台建应用,权限必须选「读和写」,否则拿不到发推能力。应用建完会拿到三组常量:API_KEY、API_SECRET、OAUTH_CALLBACK,把它们写进 config/config.php 供后续调用。 SDSS 用 TwitterOAuth 这个 PHP 库走 OAuth 2.0 流程,若之前用 Composer 装 Slim 时带了配套 composer.json,该库已在 vendor 目录,不必另下。流程核心是:用户访问 /tweet-signals,系统用 API_KEY 和 API_SECRET 换 request_token,再跳转到 Twitter 授权页。 授权后回调里要落库的是 access_token 和 access_token_secret,这两块是长期有效的,用户注销前 SDSS 不会碰账户密码。下面这张表专门存授权推友,twitter_id 用 VARCHAR(255) 足够覆盖普通用户 ID 字符串。 [CODE] 段里的 CREATE TABLE 建了 twitterers 表,id 自增主键,access_token 与 access_token_secret 用 TEXT 存原文。POST /signal/add 那段把 EA 信号(ea_id、symbol、operation、value)插进 signals 表,并返回带新 id 的 JSON——这部分接上前面章节的 RESTful 服务就能跑。 别把密钥写死在公网代码 示例里的 API_KEY 'akMnfXR45MkoaWbZoPiu3' 只是演示,真实部署务必从环境变量或加密配置读,泄露后任何人可冒用你的 Twitter 应用发推。

MQL5 / C++
class=class="str">"cmt">// Twitter OAuth
define(&class="macro">#x27;API_KEY&class="macro">#x27;, &class="macro">#x27;akMnfXR45MkoaWbZoPiu3&class="macro">#x27;);
define(&class="macro">#x27;API_SECRET&class="macro">#x27;, &class="macro">#x27;45Mkoa54NcvQRBbf119qWerty0DnIW45MncvFgqw&class="macro">#x27;);
define(&class="macro">#x27;OAUTH_CALLBACK&class="macro">#x27;, &class="macro">#x27;http:class=class="str">"cmt">//api.laplacianlab.local/twitter/oauth_callback&class="macro">#x27;);
CREATE TABLE IF NOT EXISTS twitterers(
				id mediumint UNSIGNED NOT NULL AUTO_INCREMENT,
				twitter_id VARCHAR(class="num">255),
				access_token TEXT,
				access_token_secret TEXT,
				created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
				PRIMARY KEY(id)
) ENGINE=InnoDB;
<?php
class=class="str">"cmt">// Bootstrap logic
require_once &class="macro">#x27;../config/config.php&class="macro">#x27;;
set_include_path(get_include_path() . PATH_SEPARATOR . APPLICATION_PATH . &class="macro">#x27;/vendor/&class="macro">#x27;);
set_include_path(get_include_path() . PATH_SEPARATOR . APPLICATION_PATH . &class="macro">#x27;/model/&class="macro">#x27;);
require_once &class="macro">#x27;slim/slim/Slim/Slim.php&class="macro">#x27;;
require_once &class="macro">#x27;abraham/twitteroauth/twitteroauth/twitteroauth.php&class="macro">#x27;;
require_once &class="macro">#x27;DBConnection.php&class="macro">#x27;;
use \Slim\Slim;
session_start();
Slim::registerAutoloader();
$app = new Slim();
$app->response->headers->set(&class="macro">#x27;Content-Type&class="macro">#x27;, &class="macro">#x27;application/json&class="macro">#x27;);
class=class="str">"cmt">// RESTful API methods
class=class="str">"cmt">// POST signal/add
$app->post(&class="macro">#x27;/signal/add&class="macro">#x27;, function() {  
    $request = Slim::getInstance()->request();      
    $signal = json_decode($request->getBody());
    $sql = &class="macro">#x27;INSERT INTO signals(id_ea, symbol, operation, value) VALUES(&class="macro">#x27;
            . mysql_real_escape_string($signal->ea_id) . ",&class="macro">#x27;"
            . mysql_real_escape_string($signal->symbol) . "&class="macro">#x27;,&class="macro">#x27;"
            . mysql_real_escape_string($signal->operation) . "&class="macro">#x27;,"
            . mysql_real_escape_string($signal->value) . &class="macro">#x27;)&class="macro">#x27;;
    DBConnection::getInstance()->getHandler()->query($sql);
    $signal->id = DBConnection::getInstance()->getHandler()->insert_id;
    echo json_encode($signal);
});
class=class="str">"cmt">// More API methods here!.., according to your API spec
class=class="str">"cmt">// Twitter OAuth flow
class=class="str">"cmt">// This method is for users to give permissions to Laplacianlab&class="macro">#x27;s SDSS to tweet 
class=class="str">"cmt">// on their behalf.
$app->get(&class="macro">#x27;/tweet-signals&class="macro">#x27;, function() use($app) {  
    if (empty($_SESSION[&class="macro">#x27;twitter&class="macro">#x27;][&class="macro">#x27;access_token&class="macro">#x27;]) || empty($_SESSION[&class="macro">#x27;twitter&class="macro">#x27;][&class="macro">#x27;access_token_secret&class="macro">#x27;]))
    {
        $connection = new TwitterOAuth(API_KEY, API_SECRET);
        $request_token = $connection->getRequestToken(OAUTH_CALLBACK);
        if ($request_token)
        {
            $_SESSION[&class="macro">#x27;twitter&class="macro">#x27;] = array(
                &class="macro">#x27;request_token&class="macro">#x27; => $request_token[&class="macro">#x27;oauth_token&class="macro">#x27;],

常见问题

建议按信号源、订单、状态三张表拆分,用信号 ID 做外键关联,避免把持仓和已平历史混在一张表里。
用 JSON 更稳,EA 侧直接序列化订单字段,接口侧用 body 解析,少踩编码坑,也方便以后扩字段。
可以,小布能定时探你的本地 API 并比对最近信号时间,异常时直接推送提醒,不用自己写监控脚本。
在接口层打印原始 body 并校验必填字段,EA 侧加发送回执,两边时间差超阈值就重发。
多半是签名基串里 query 参数没排序或时间戳偏慢,用库生成签名别手拼,服务器时间先校准确。