用于时间序列挖掘的数据标签(第 5 部分):使用 Socket 在 EA 中进行应用和测试·综合运用
「挂单撤销与反手建仓的分支处理」
这段逻辑处在交易指令派发的中段:当待处理单类型被识别为买入挂单时,不直接开仓,而是用 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 分钟周期先看成交率再上真仓。
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 粘包未在此处理,实盘高频推送时可能概率性解析错位。
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,再确认发送端有没有走这个封包分支。外汇与贵金属杠杆高,回测结论仅代表历史样本,实盘可能显著偏离。
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 内直接加载模型、甩掉套接字依赖,部署门槛会降一截。眼下先把它当实验室器材,别供成圣杯。