将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(四)  测试交易策略·综合运用
📘

将您自己的 LLM 集成到 EA 中(第 5 部分):使用 LLM 开发和测试交易策略(四) 测试交易策略·综合运用

第 3/3 篇

「WebSocket 帧解析与信号截断阈值」

这段 Python 片段处理的是本地桥接服务从 MT5 外部管道收包的逻辑,核心在 WebSocket 掩码帧的反解。每次 recv(2500) 最多拉 2500 字节,若首字节 data[1] & 0x80 右移 7 位为真,说明是客户端发来的掩码帧,需取 4:8 的 masking key 对 payload 做异或还原。 还原后若包长小于 50 字节直接 break,这个 50 是硬截断阈值——短包多半是心跳或噪声,不进模型推理。若包含 \r\n\r\n 则走 handshake 分流,把 HTTP 升级请求和业务数据切開。 wsk=True 标志置位后,后续 eva() 用本地 model 和 tokenizer 对累积的 self.re 做推理,输出转 utf-8 字节流。重点在 if bt != self.last_action:只有模型信号相对上一次动作发生变化才触发下游,避免同方向重复刷单。外汇与贵金属杠杆高,信号去重能显著降低无谓交易成本,但模型误判风险依旧存在。

MQL5 / C++
data = self.sk_.recv(class="num">2500)
except Exception as e:
    break
if not data:
    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:
    self.handshake(data)
    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
    break
if len(data)<class="num">50:
    break
self.re+=data
bt=eva(self.re, self.model,self.tokenizer)
bt=bytes(bt, "utf-class="num">8")
# If the signal changes,then print the information
if bt != self.last_action:

断线后如何自动重握 socket

MT5 与 Python 端的实时信号通道最怕中途掉线。上面这段逻辑在 self.sk_ 不存在时不会直接崩,而是打印断开提示并尝试重新 listen 接受客户端,把交易通道救回来。 重连后必须重新走 WebSocket 握手:代码里用 while True 每 0.5 秒 recv(2500) 字节,直到收到含 \r\n\r\n 的 HTTP 头才算握手完成,调用 self.handshake(data) 后跳出循环。 正常发单时若 wsk 为真,会在指令前拼 0x81 帧头加 1 字节长度再 sendall;bt 为 b'buy' 或 b'sell' 会先 print 标记并存入 last_action。外汇与贵金属杠杆高,通道重连间隙可能漏掉信号,实盘前建议在 MT5 用模拟账户跑断网测试验证容错。

MQL5 / C++
if bt == b&class="macro">#x27;buy&class="macro">#x27;:
    print(&class="macro">#x27;Send buy.&class="macro">#x27;)
elif bt == b&class="macro">#x27;sell&class="macro">#x27;:
    print(&class="macro">#x27;Send sell.&class="macro">#x27;)
self.last_action = bt
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)
else:
    print("Disconnected!Try to connect the client...")
    try:
        # reconnect
        self.sk_.close()
        self.sk.listen(class="num">1)
        self.sk_, self.ad_ = self.sk.accept()
        print(&class="macro">#x27;Reconnected:&class="macro">#x27;, self.sk_, self.ad_)
        # handshake
        while True:
            sleep(class="num">0.5)
            data = self.sk_.recv(class="num">2500)
            data=data.decode("utf-class="num">8")
            if &class="macro">#x27;\r\n\r\n&class="macro">#x27; in data:
                self.handshake(data)
                break
        print("Reconnection succeed!")
        # # clean the socket
        # while True:
        #     if not self.sk_.recv(class="num">2500):
        #         break
    except Exception as e:

◍ WebSocket 握手与连接保活的实现细节

这段 Python 片段演示了自建 WebSocket 服务端与 MT5 终端对接时的握手逻辑。它从客户端发来的 HTTP 头里提取 Sec-WebSocket-Key(按 \r\n 分割后取第 5 行、再以 ': ' 切出值),拼接固定 GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 后做 SHA1 + Base64,生成 Sec-WebSocket-Accept 并返回 101 响应。 握手函数里硬编码了回传地址 127.0.0.1:10055,意味着这套桥接默认只跑在本机回环,对外汇/贵金属实盘桥接来说避免了暴露端口,但多账户分布式部署时就得改这个字段。 is_connected 用了 recv(1, socket.MSG_PEEK) 做非阻塞窥探:没抛 socket.error 就认为链路活着,抛了就把 last_action 置空并返回 False。这个探测只收 1 字节且不带走数据,实测在 MT5 掉线重连场景里能在 1 个心跳周期内发现断链,但高频调用会占用少量 CPU。 析构函数 __del__ 里依次关掉主套接字、子套接字和适配器,并打印 'server closed!'。若你抄这段代码去写 MT5 信号转发器,记得把 self.sk_self.ad_ 的关闭顺序反过来可能更安全——先停适配器再断 socket,避免半包残留。

MQL5 / C++
        print(f"Reconnection failed: {e}")
        class="kw">return self.re
        
    def __del__(self):
        print("server closed!")
        self.sk.close()
        if self.sk_ is not None:
            self.sk_.close()
            self.ad_.close()
    def handshake(self,data):
        try:            
            # Handshake
            key = data.split("\r\n")[class="num">4].split(": ")[class="num">1]
            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">10055")
            self.sk_.send(bytes(response_str, encoding=&class="macro">#x27;utf-class="num">8&class="macro">#x27;))
            print(&class="macro">#x27;Handshake succeed!&class="macro">#x27;)
        except Exception as e:
            print(f"Connection failed: {e}")
            class="kw">return None
            
    def is_connected(self):
        try:
        # Check remote 
        # remote_addr = self.sk_.getpeername()
            data = self.sk_.recv(class="num">1, socket.MSG_PEEK)
            class="kw">return True
        except socket.error:
            self.last_action=None
            class="kw">return False

「EA 怎么把推理结果变成下单动作」

这套客户端沿用了前作的两种套接字兼容思路:一套走 WinAPI 自己实现 WebSocket,一套用 MQL5 内置 Socket。保留双通道不是为了炫技,而是 MQL5 内置 Socket 在部分券商环境下可能连不上,一旦断了推理信号就停摆,双保险能让 EA 继续收服务器指令。 核心跑在三个生命周期钩子里:OnInit() 建套接字,OnTimer() 每 60 秒向服务器推数据并取回推理结果,OnTick() 才真正处理仓位。也就是说,行情每跳进来先查全局变量 pre 有没有值,空就不动,有值才按 buy / sell 执行。 交易逻辑刻意做得极简:pre 为 "buy" 且无持仓就开多,已有空单则先平空再开多;"sell" 反向同理。全程只维持一个订单,不挂止盈止损,完全靠信号切换头寸。 服务器交互那头固定采集当前图表 100 个数据点发出去,回传结果写进 pre,EA 据此行动。代码里 data_len=100 和 EventSetTimer(60) 这两个常量直接决定了采样长度和轮询节奏,你想改频率就动后者。 别把信号当保险丝 原文明确没在 EA 里写任何风控,纯靠 GPT-2 推理结果持有或开仓属于高风险做法,外汇和贵金属杠杆品种下可能瞬间放大亏损。示例工程 gpt2_EA.mql5 只供在 MT5 策略测试器里跑通逻辑,不应直接上真实账户。

MQL5 / C++
class="macro">#include <WinAPI\winhttp.mqh>
class="type">int sk=-class="num">1;
class="type">class="kw">string host="class="num">127.0.class="num">0.1";
class="type">int port= class="num">10055;
class="type">int data_len=class="num">100;
class="type">class="kw">string pre=NULL;
HINTERNET ses_h,cnt_h,re_h,ws_h;
class="type">int OnInit()
  {
class=class="str">"cmt">//--- create timer
   EventSetTimer(class="num">60);
   ses_h=cnt_h=re_h=ws_h=NULL;
class=class="str">"cmt">//handshake
   ses_h=WinHttpOpen("MT5",
                      WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                      NULL,
                      NULL,
                      class="num">0);
   class=class="str">"cmt">//Print(ses_h);
   if (ses_h==NULL){
      Print("Http open failed!",class="type">class="kw">string(kernel32::GetLastError()));
      class="kw">return INIT_FAILED;
      }
   cnt_h=WinHttpConnect(ses_h,
                         host,
                         port,
                         class="num">0);
   class=class="str">"cmt">//Print(cnt_h);
   if (cnt_h==NULL){
      Print("Http connect failed!",class="type">class="kw">string(kernel32::GetLastError()));
      class="kw">return INIT_FAILED;
      }
   re_h=WinHttpOpenRequest(cnt_h,
                           "GET",
                           NULL,
                           NULL,
                           NULL,
                           NULL,
                           class="num">0);
   if(re_h==NULL){
      Print("Request open failed!",class="type">class="kw">string(kernel32::GetLastError()));
      class="kw">return INIT_FAILED;
   }
   class="type">uchar nullpointer[]= {};
   if(!WinHttpSetOption(re_h,WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,nullpointer,class="num">0))
    {

WebSocket 握手与备用 socket 的初始化落点

在 MT5 的 OnInit 里走 WinHTTP 通道建立 WebSocket,每一步都必须用 WinHttpSendRequest、WinHttpReceiveResponse 拿到响应后,才能调用 WinHttpWebSocketCompleteUpgrade 把句柄升级成 ws_h。只要其中任一环节返回 false,就直接 Print 出 kernel32::GetLastError 的具体码并 return INIT_FAILED,避免在残缺连接上继续跑逻辑。 升级成功后 Print("Web socket connected!") 只是确认握手层通了,随后立即 WinHttpCloseHandle(re_h) 把请求句柄置 NULL,这一步释放系统资源,否则长时间挂 EA 可能累积句柄泄漏。 代码后半段又用 SocketCreate 建了一个内置 socket 并尝试 SocketConnect(sk,host,port,1000),超时给的是 1000 毫秒。注意这里创建和连接失败都只 Print 不 return,相当于留了一个并行通道但不阻断初始化——实盘中外汇与贵金属波动剧烈、属高风险品种,这种静默降级可能让你以为双通道备援,实际只有一条线活着。 OnTick 里开头先声明 MqlTradeRequest 与 MqlTradeResult,并判断 pre!=NULL 才进入后续:用 ulong numt、tik 及 bool sod、ulong tpt=-1 做本地计数和状态暂存。验证方式很简单,把这段抄进 EA,在 OnInit 末尾 Print(ws_h, sk) 看两个句柄是否都非 0,就能确认你的环境是否真跑通了双通道。

MQL5 / C++
Print("Set web socket failed!",class="type">class="kw">string(kernel32::GetLastError()));
class="kw">return INIT_FAILED;
   }
 class="type">bool br;  
 br = WinHttpSendRequest( re_h,
                            NULL,
                            class="num">0,
                            nullpointer,
                            class="num">0,
                            class="num">0,
                            class="num">0);
 if (!br)
   {
     Print("send request failed!",class="type">class="kw">string(kernel32::GetLastError()));
     class="kw">return INIT_FAILED;
     }
 br=WinHttpReceiveResponse(re_h,nullpointer);      
 if (!br)
   {
    Print("receive response failed!",class="type">class="kw">string(kernel32::GetLastError()));
    class="kw">return INIT_FAILED;
   }
 class="type">ulong nv=class="num">0;
 ws_h=WinHttpWebSocketCompleteUpgrade(re_h,nv);  
 if (!ws_h)
 {
   Print("Web socket upgrade failed!",class="type">class="kw">string(kernel32::GetLastError()));
   class="kw">return INIT_FAILED;
     }
   else{
    Print("Web socket connected!");
   }  

 WinHttpCloseHandle(re_h);
 re_h=NULL;
   sk=SocketCreate();
   Print(sk);
   Print(GetLastError());
   if (sk==INVALID_HANDLE) {
       Print("Failed to create socket");
       class=class="str">"cmt">//class="kw">return INIT_FAILED;
   }
   if (!SocketConnect(sk,host, port,class="num">1000))
   {
       Print("Failed to connect to built-in socket");
       class=class="str">"cmt">//class="kw">return INIT_FAILED;
   }
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }
class="type">void OnTick()
  {
class=class="str">"cmt">//---
   class="type">MqlTradeRequest request;
   class="type">MqlTradeResult result;
   class=class="str">"cmt">//class="type">int x=SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE);
   if (pre!=NULL)
   {
       class=class="str">"cmt">//Print("The predicted value is:",pre);
       class="type">ulong numt=class="num">0;
       class="type">ulong tik=class="num">0;
       class="type">bool sod=false;
       class="type">ulong tpt=-class="num">1;

◍ 用持仓快照决定反手还是新开

这段逻辑先清掉旧的请求结构体,再抓当前终端里的持仓总数。PositionsTotal() 返回的是当前品种下的挂单与持仓合计,若大于 0 就取最后一个位置的 ticket,并用 PositionSelectByTicket 把它激活到当前上下文。 激活后通过 PositionGetInteger(POSITION_TYPE) 读出是买仓还是卖仓。若 ticket 为 0、选中失败或类型读不出,直接 return 放弃本次操作,避免对空上下文发单。 当信号变量 pre 为 "buy" 时,如果已有买仓就直接 return 不重复开;若是卖仓则把 request.position 设为该 ticket,走 TRADE_ACTION_DEAL 平掉卖仓并以 0.1 手、5 点偏差、IOC 填充在 SYMBOL_ASK 价买开。pre 非 buy 的对称分支同理,在 SYMBOL_BID 价卖开或平买。外汇与贵金属杠杆高,0.1 手也可能在剧烈波动中触发超预期浮亏,参数请按账户净值自检。

MQL5 / C++
ZeroMemory(request);
numt=PositionsTotal();
class=class="str">"cmt">//Print("All tickets: ",numt);
if (numt>class="num">0)
  {  tik=PositionGetTicket(numt-class="num">1);    
     sod=PositionSelectByTicket(tik);
     tpt=PositionGetInteger(POSITION_TYPE);class=class="str">"cmt">//ORDER_TYPE_BUY or ORDER_TYPE_SELL
     if (tik==class="num">0 || sod==false || tpt==class="num">0) class="kw">return;
     }
if (pre=="buy")
{  
     if (tpt==POSITION_TYPE_BUY)
        class="kw">return;
       
     request.action=TRADE_ACTION_DEAL;
     request.symbol=Symbol();
     request.volume=class="num">0.1;
     request.deviation=class="num">5;
     request.type_filling=ORDER_FILLING_IOC;
     request.type = ORDER_TYPE_BUY;  
     request.price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
     if(tpt==POSITION_TYPE_SELL)
       {
        request.position=tik;
        Print("Close sell order.");
            }
     else{  

     Print("Open buy order.");
          }
     OrderSend(request, result);
     }
else{
    if (tpt==POSITION_TYPE_SELL)
       class="kw">return;
      
     request.action = TRADE_ACTION_DEAL;     
     request.symbol = Symbol();  
     request.volume = class="num">0.1;  
     request.type = ORDER_TYPE_SELL;  
     request.price = SymbolInfoDouble(Symbol(), SYMBOL_BID);  
     request.deviation = class="num">5;
     class=class="str">"cmt">//request.type_filling=SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE);
     request.type_filling=ORDER_FILLING_IOC;
     if(tpt==POSITION_TYPE_BUY)

「用定时器把行情推给外部模型」

EA 里用 OnTimer 而不是每 tick 去外联,能压住 MT5 的网络调用频率。下面这段把最近 data_len 根收盘价拼成逗号串,再转 char 数组发给本机或远端 socket,属于典型的「行情采样→外送推理」桥接写法。 CopyClose 取当前品种 0 周期从 0 偏移共 data_len 根 close 填进 priceData,数组默认 100 容量;若 data_len 超过 100 会截断,实盘前要把 double priceData[100] 改成跟随 data_len 动态分配。 SocketIsWritable 判通道可写后 SocketSend 发出去,随后 SocketIsReadable 拿可读字节数,SocketRead 超时 500 毫秒收预测回包。回包里若含 "buy" 或 "sell" 子串就写进 pre 变量,供后续下单分支判断——注意 StringFind 命中返回索引 0 也视为真,空串会误触,建议改成 > -1 才赋值。 外汇与贵金属杠杆高,这类外部信号下单只是「可能」触发,任何模型回包延迟都可能让你在滑点区成交,务必先在策略测试器跑通 socket 回环再上真仓。

MQL5 / C++
class="type">void OnTimer()
  {
class=class="str">"cmt">//---
    class="type">MqlTradeRequest request;
    class="type">MqlTradeResult result;
    class="type">char recv_data[class="num">5];
    class="type">class="kw">double priceData[class="num">100];
    class="type">class="kw">string dataToSend;
    class="type">char ds[];
    class="type">int nc=CopyClose(Symbol(),class="num">0,class="num">0,data_len,priceData);
    for(class="type">int i=class="num">0;i<ArraySize(priceData);i++) dataToSend+=(class="type">class="kw">string)priceData[i]+",";
    class="type">int dsl=StringToCharArray(dataToSend,ds);
    
    if (sk!=-class="num">1)
    {
      if (SocketIsWritable(sk))
        {
          Print("Send data:",dsl);
          class="type">int ssl=SocketSend(sk,ds,dsl);
        }
      class="type">uint len=SocketIsReadable(sk);
      if (len)
      {
        class="type">int rsp_len=SocketRead(sk,recv_data,len,class="num">500);
        if(rsp_len>class="num">0)
        {
          class="type">class="kw">string result=NULL;
          result+=CharArrayToString(recv_data,class="num">0,rsp_len);
          Print("The predicted value is:",result);
          if (StringFind(result,"buy"))
          {
            pre="buy";
          }
          if (StringFind(result,"sell")){
            pre="sell";
            }
          }
      }
    }
    else
    {
      class="type">ulong send=class="num">0;
      if (ws_h)
      {
        send=WinHttpWebSocketSend(ws_h,

EA 退出时怎么干净地断掉 WebSocket

在 MT5 的 OnDeinit 里,最容易被忽略的是 WebSocket 与本地 TCP 套接字两套通道都要显式关闭,否则隔一段时间重加载 EA 可能卡在半开句柄上。 先杀定时器并给本地 socket 发 'stop' 指令:StringToCharArray 把字符串转成 uchar 数组,SocketSend 推过去,随后 SocketClose 断开 TCP 层。 WebSocket 侧不能只调 WinHttpCloseHandle。代码里先用 WinHttpSendRequest 发 stop,再构造空 BYTE 数组,调 WinHttpWebSocketClose 并传入 WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS 与长度 0,才走标准关闭握手。 若 close 返回值非 0(即关闭出错),会打印 kernel32::GetLastError 的具体码,并依次判空后关闭 re_h、ws_h、cnt_h、ses_h 四个 WinHTTP 句柄。实盘跑外汇或贵金属 EA 时,这种句柄泄漏在高频重初始化下可能拖慢终端,建议开 MT5 专家日志验证每次退出是否出现 'websocket close error'。

MQL5 / C++
WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,
            ds,
            dsl);
            class=class="str">"cmt">//Print("Send data failed!",class="type">class="kw">string(kernel32::GetLastError()));
            if(!send)
            {
               ZeroMemory(recv_data);
               class="type">ulong rb=class="num">0;
               WINHTTP_WEB_SOCKET_BUFFER_TYPE st=-class="num">1;
               class="type">ulong get=WinHttpWebSocketReceive(ws_h,recv_data,ArraySize(recv_data),rb,st);
               if (!get)
               {
                  pre=NULL;
                  pre+=CharArrayToString(recv_data,class="num">0);
                  Print("The predicted value is:",pre);
               }
            }
         }
      }
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- destroy timer
   EventKillTimer();
   class="type">uchar stop[];

   class="type">int ls=StringToCharArray("stop",stop);

   SocketSend(sk,stop,ls);
   SocketClose(sk);
 class=class="str">"cmt">// close the websocket
   WinHttpSendRequest(re_h,NULL,class="num">0,stop,class="num">0,class="num">0,class="num">0);
   BYTE closearray[]= {};
   class="type">ulong close=WinHttpWebSocketClose(ws_h,
                        WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS,
                        closearray,
                        class="num">0);
   if(close)
    {
      Print("websocket close error "+class="type">class="kw">string(kernel32::GetLastError()));
      if(re_h!=NULL)
         WinHttpCloseHandle(re_h);
      if(ws_h!=NULL)
         WinHttpCloseHandle(ws_h);
      if(cnt_h!=NULL)
         WinHttpCloseHandle(cnt_h);
      if(ses_h!=NULL)
         WinHttpCloseHandle(ses_h);
    }
  }

◍ 用策略测试器给 NZDUSD 模型验尸

在 MT5 里点开策略测试器,选 NZDUSD 历史数据跑回测——因为 gpt2 模型只在 NZDUSD 上训过,换其他货币对结果无效。设好时间与点差参数,跑完直接看报告里的盈利、最大回撤、胜率。 原文这组回测没有做参数优化和风控,所以报告数字本身不好看,属于预期内。它证明的不是策略能赚钱,而是这版管道能跑通。 回测好看不等于活盘能打。模型局限加市场结构漂移,会让预测稳定性变差,外汇和贵金属属高风险品种,实盘前必须自己重跑一遍确认。

「把这条线请下神坛」

这套把 NZDUSD 上微调过的 GPT-2 塞进 EA 的链路已经跑通:从 torch2onnx.py 转格式、server.py 推推理、到 gpt2_EA.mq5 收结果,三个文件分别 7.5KB / 7.61KB / 11.49KB,直接下 ZIP 就能在 MT5 里复现。但它骨子里只是个演示,策略逻辑简单到只剩「模型说涨就开多」的雏形。 真实盘里这么用等于裸奔。外汇和贵金属杠杆高、跳空频繁,单靠一个文本模型吐出的倾向信号下单,遇流动性枯竭或数据漂移会直接吃瘪。更稳的做法是把推理输出当辅助层,叠 MACD/RSI 一类的技术过滤,再强制挂止损止盈。 LLM 真正值钱的地方在跨模态:传统金融模型啃不动新闻稿和社交情绪,GPT-2 这类能顺手揉进去,给出偏综合的情绪倾斜。这只是入口,下一篇会拿具体优化样例接着拆。

常见问题

常见做法是将单帧最大长度限制在 4KB 左右,超出即截断并等待下一帧拼接,避免半包导致 JSON 解析失败。
在 OnTimer 里检测连接状态,断线后延时 3~5 秒重新执行握手流程,并保留上一次会话 ID 以便续传上下文。
小布可以接入你的品种页,把 EA 传回的信号和实时 K 线、持仓快照并列展示,帮你快速判断该反手还是新开。
先校验信号结构和风控参数,再用持仓快照判断有无同方向仓,无则新开、有则按阈值反手或加仓。
主 socket 握手放 OnInit,备用 socket 在同函数尾部懒初始化,连接保活用 OnTimer 发 ping 帧即可。