用于时间序列挖掘的数据标签(第 5 部分):使用 Socket 在 EA 中进行应用和测试·综合运用
📘

用于时间序列挖掘的数据标签(第 5 部分):使用 Socket 在 EA 中进行应用和测试·综合运用

第 3/3 篇

「挂单撤销与反手建仓的分支处理」

这段逻辑处在交易指令派发的中段:当待处理单类型被识别为买入挂单时,不直接开仓,而是用 TRADE_ACTION_REMOVE 把原 ticket 撤掉,并在日志打印 Close buy order. 作为痕迹。 若不是买入挂单,则走 else 分支重置请求结构体并反手做空:市价单以 SYMBOL_BID 为入场,volume 固定为 1 手,deviation 给 5 点容差,填充方式选 ORDER_FILLING_IOC,随后打印 OPen sell order. 注意原文拼写漏了 S,实盘复制时建议修正。 末尾 OrderSend(request, result) 把两种分支的统一出口收口;外汇与贵金属杠杆高,这类无条件反手在滑点扩大时可能触发连续止损,上 MT5 用策略测试器跑 EURUSD 1 分钟周期先看成交率再上真仓。

MQL5 / C++
else if(tpt==ORDER_TYPE_BUY)
  {
  request.order=tik;
  request.action=TRADE_ACTION_REMOVE;
  Print("Close buy order.");
  }
  else{
  ZeroMemory(request);
  request.action = TRADE_ACTION_DEAL;
  request.symbol = Symbol();
  request.volume = class="num">1;
  request.type = ORDER_TYPE_SELL;
  request.price = SymbolInfoDouble(Symbol(), SYMBOL_BID);
  request.deviation = class="num">5;
  request.type_filling=ORDER_FILLING_IOC;
  Print("OPen sell order.");
  }
OrderSend(request, result);

WebSocket 握手与掩码帧的拆解逻辑

这段 Python 片段展示了在本地 8989 端口上,如何从一个原始 socket 缓冲里认出 WebSocket 握手包并回 101 响应。它先判断 data 是否为空,空了就直接 break,避免死循环卡住接收线程。 当第二个字节的高位(data[1] & 0x80 右移 7)为 1 时,说明收到了掩码帧:从 data[0] 拆出 FIN 位和 opcode,从 data[4:8] 取 4 字节掩码密钥,data[8:] 才是真实负载。随后用 payload[i] ^ mask[i % 4] 逐字节异或还原明文,这是 RFC6455 规定的客户端发往服务端必须带掩码的解码方式。 若高位为 0,则按 utf-8 直接 decode,并查找 '\r\n\r\n' 作为握手头与体的分界。从分割后的第 5 行(index 4)取 Sec-WebSocket-Key,拼上固定 GUID '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 做 sha1+base64,回传 Sec-WebSocket-Accept。该 GUID 是协议强制常量,错一个字符握手都会失败。 若解密后内容含 'stop' 就置 stop 标志并跳出;最后用 len(data) < 200 做了一道简易长度守卫,短包才继续后续处理。外汇与贵金属行情转发若走这套私有 ws 桥,需注意 socket 粘包未在此处理,实盘高频推送时可能概率性解析错位。

MQL5 / C++
if not data:
    class="kw">break
if (data[class="num">1] & 0x80) >> class="num">7:
    fin = (data[class="num">0] & 0x80) >> class="num">7 # FIN bit
    opcode = data[class="num">0] & 0x0f # opcode
    masked = (data[class="num">1] & 0x80) >> class="num">7 # mask bit
    mask = data[class="num">4:class="num">8] # masking key
    payload = data[class="num">8:] # payload data
    print(&class="macro">#x27;fin is:{},opcode is:{},mask:{}&class="macro">#x27;.format(fin,opcode,masked))
    message = ""
    for i in range(len(payload)):
        message += chr(payload[i] ^ mask[i % class="num">4])
    data=message
    wsk=True
else:
    data=data.decode("utf-class="num">8")
if &class="macro">#x27;\r\n\r\n&class="macro">#x27; in data:
    key = data.split("\r\n")[class="num">4].split(": ")[class="num">1]
    print(key)
    GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
    ac = base64.b64encode(hashlib.sha1((key+GUID).encode(&class="macro">#x27;utf-class="num">8&class="macro">#x27;)).digest())
    response_tpl="HTTP/class="num">1.1 class="num">101 Switching Protocols\r\n" \
        "Upgrade:websocket\r\n" \
        "Connection: Upgrade\r\n" \
        "Sec-WebSocket-Accept: %s\r\n" \
        "WebSocket-Location: ws:class=class="str">"cmt">//%s/\r\n\r\n"
    response_str = response_tpl % (ac.decode(&class="macro">#x27;utf-class="num">8&class="macro">#x27;), "class="num">127.0.class="num">0.1:class="num">8989")
    self.sk_.send(bytes(response_str, encoding=&class="macro">#x27;utf-class="num">8&class="macro">#x27;))
    
    data=data.split(&class="macro">#x27;\r\n\r\n&class="macro">#x27;,class="num">1)[class="num">1]
if "stop" in data:
    self.stop=True
    class="kw">break
if len(data)<class="num">200:

◍ 回测结果经 WebSocket 回传的封包细节

这段逻辑出现在本地模型跑完增量回测之后,核心是把 eva() 算出的字符串结果塞回交易终端。注意 bt=bytes(bt,"utf-8") 这一步:MT5 的 Python 桥接层只认字节流,漏掉编码转换会直接抛 TypeError。 当 wsk 标志位为真,代码手动拼了一个 0x81 开头的帧头,再用 struct.pack('B',lgt) 把负载长度压成单字节跟在后面。也就是说单帧回传上限是 255 字节,超出会静默截断,这是实盘调试时容易踩的坑。 最后 self.sk_.sendall(bt) 做阻塞发送,函数返回累积的 self.re。若你在 MT5 里收不到完整回测报告,先查长度是否越过 255,再确认发送端有没有走这个封包分支。外汇与贵金属杠杆高,回测结论仅代表历史样本,实盘可能显著偏离。

MQL5 / C++
              class="kw">break
            self.re+=data
            bt=eva(self.re, self.model)
            bt=bytes(bt, "utf-class="num">8")
            if wsk:
                tk=b&class="macro">#x27;\x81&class="macro">#x27;
                lgt=len(bt)
                tk+=class="kw">struct.pack(&class="macro">#x27;B&class="macro">#x27;,lgt)
                bt=tk+bt
            self.sk_.sendall(bt)
            class="kw">return self.re

「把工具请下神坛」

这套服务端加 EA 客户端的回测链路,最难啃的是 WebSocket 跨进程通信,工程复杂度远高于普通指标。跟着步骤走大概率能跑通,但附带的 socket_test.mq5(21.34 KB)、server.py(4 KB)只是验证模型推理闭环的的最小示例。 真实盘环境里,网络抖动、模型延迟、断线重连任一处没兜底都可能让信号失效,外汇和贵金属又是高杠杆高风险品种,直接挂实盘等于裸奔。 下一篇如果改成 EA 内直接加载模型、甩掉套接字依赖,部署门槛会降一截。眼下先把它当实验室器材,别供成圣杯。

常见问题

在分支处理里判断 OrderSelect 返回 false 且超时,先调用 OrderDelete 撤单,再用 OrderSend 以市价反手建仓,注意设好止损和手数。
握手后服务端发来的帧若 MASK 位为 0 则直接读载荷;客户端发出的帧必须置 MASK=1 并用 4 字节掩码异或解码,顺序按 RFC6455 逐字节处理即可。
可以。小布能接收回传的封包并解析字段,把买卖点与撤单反手事件转成白话弹窗,你只管看结论不用管底层帧结构。
至少带时间戳、品种、动作类型、价格、手数、盈亏,用固定分隔符封包,接收端按索引拆开就能直接写日志。
不靠谱。回测环境无滑点延迟,实盘价差和拒单会改写分支走向,先把工具请下神坛,拿模拟盘跑两周再谈调参。