基于套接字(Sockets)的Twitter情绪分析·进阶篇
📡

基于套接字(Sockets)的Twitter情绪分析·进阶篇

(2/3)· 当EA卡在30分钟轮询与socket握手之间,情绪分数早已不是推文本身的问题

偏理论 第 2/3 篇

不少交易者把社交情绪EA当成黑箱,以为接上API就能自动开仓。实际上MT5与Python之间靠裸套接字传数据,任何一次OnInit里的连接失败都会让整条信号链静默失效,而日志里往往只剩一个超时。

◍ 情绪信号落地成单的逻辑骨架

把推文情绪转成 MT5 下单动作,核心是一段条件分支:情绪值大于 0 走 buy 分支,小于 0 走 sell 分支,等于 0 则不做任何事。这里 stop_loss 用 InpStopLoss 乘以 _Point 动态算出,点值随品种浮动,黄金和外汇直盘的点值差可能差出十几倍,参数不能直接抄。 开仓前必须用 PositionSelect(symbol1) 查重,已持仓就跳过并打印提示,避免同方向重复加仓把风险敞口叠厚。外汇与贵金属波动剧烈,重复开仓可能让回撤超出预期。 Buy 和 Sell 的挂单都带了止损止盈:buy 的止损放在 price - stop_loss,止盈在 price + take_profit;sell 则反向。下单失败不静默,用 GetLastError() 把错误码打出来,方便你直接在 MT5 终端排查。 和 Python 服务端通信靠本地 socket,InitSocket() 里绑定 127.0.0.1:65432、超时 5000 毫秒。若 SocketCreate 返回负值或 Connect 不成功,函数直接返 -1,EA 起不来时先查这个端口有没有被 Python 进程占用。

MQL5 / C++
class="type">class="kw">double stop_loss = InpStopLoss * _Point;

if(tweet_sentiment > class="num">0)
  {
   class=class="str">"cmt">// Buy if sentiment is positive
   if(PositionSelect(symbol1))
     {
      Print("Position already open. Skipping buy.");
     }
   else
     {
      if(trade.Buy(InpLot, symbol1, price, price - stop_loss, price + take_profit))
       Print("Buying ", InpLot, " lots of ", symbol1);
      else
       Print("Failed to place buy order. Error: ", GetLastError());
     }
  }
else if(tweet_sentiment < class="num">0)
  {
   class=class="str">"cmt">// Sell if sentiment is negative
   if(PositionSelect(symbol1))
     {
      Print("Position already open. Skipping sell.");
     }
   else
     {
      if(trade.Sell(InpLot, symbol1, price, price + stop_loss, price - take_profit))
       Print("Selling ", InpLot, " lots of ", symbol1);
      else
       Print("Failed to place sell order. Error: ", GetLastError());
     }
  }
 }
else if(StringFind(result, "ERROR,") == class="num">0)
  {
   Print("Error received from Python server: ", result);
  }
else
  {
   Print("Unexpected response format: ", result);
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initialize socket                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int InitSocket()
  {
   class="type">int socket_handle = SocketCreate();
   if(socket_handle < class="num">0)
     {
      Print("Error creating socket");
      class="kw">return -class="num">1;
     }
   Print("Socket created successfully.");
   class=class="str">"cmt">// Connect to Python server
   class="type">bool isConnected = SocketConnect(socket_handle, "class="num">127.0.class="num">0.1", class="num">65432, class="num">5000);
   if(!isConnected)
     {
      class="type">int error = GetLastError();

「EA 与 Python 的 socket 收发细节」

在 MT5 里把策略判断甩给 Python 跑,核心就是这套 socket 通信函数。InitSocket 建连失败会直接 Print 错误码并 return -1,EA 侧必须判负,否则后面发数据全是空操作。 CommunicateWithPython 先把字符串用 StringToCharArray 转成 uchar 缓冲,再 SocketSend 发出去;返回字节数小于 0 就关 socket 退出。这里用的是 UTF-8 编码,中文指令或特征名不会乱码。 接收端设了 5000 毫秒超时:用 GetTickCount()+timeout 做截止时间,在 do-while 里靠 SocketIsReadable 探可读长度,SocketRead 拉回来再用 CharArrayToString 拼结果。循环同时受 IsStopped() 约束,策略卸载会立刻断连。 辅助函数 CharArrayToString 只是把 uchar 子段拷进 char 数组再转 string,避免一次性转整包带来偏移错乱。实盘跑之前,建议先在本地起一个 Python socket server 回显,看 Print 里 Data sent / received 的字节数对不对得上。

MQL5 / C++
Print("Error connecting to Python server. Error code: ", error);
SocketClose(socket_handle);
class="kw">return -class="num">1;
 }
 Print("Connection to Python server established.");
 class="kw">return socket_handle;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to send and receive data                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CommunicateWithPython(class="type">class="kw">string data)
 {
 class="type">int socket_handle = InitSocket();
 if(socket_handle < class="num">0)
 class="kw">return "";
 class=class="str">"cmt">// Ensure data is encoded in UTF-class="num">8
 class="type">uchar send_buffer[];
 StringToCharArray(data, send_buffer);
 class="type">int bytesSent = SocketSend(socket_handle, send_buffer, ArraySize(send_buffer));
 if(bytesSent < class="num">0)
  {
   Print("Error sending data!");
   SocketClose(socket_handle);
   class="kw">return "";
  }
 Print("Data sent: ", bytesSent);
 class="type">uint timeout = class="num">5000; class=class="str">"cmt">// class="num">5 seconds timeout
 class="type">uchar rsp[];
 class="type">class="kw">string result;
 class="type">uint timeout_check = GetTickCount() + timeout;
 do
  {
   class="type">uint len = SocketIsReadable(socket_handle);
   if(len)
     {
      class="type">int rsp_len;
      rsp_len = SocketRead(socket_handle, rsp, len, timeout);
      if(rsp_len > class="num">0)
        {
         result += CharArrayToString(rsp, class="num">0, rsp_len);
        }
     }
  }
 while(GetTickCount() < timeout_check && !IsStopped());
 SocketClose(socket_handle);
 if(result == "")
  {
   Print("No data received from Python");
   class="kw">return "";
  }

 Print("Data received from Python: ", result);
 class="kw">return result;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Helper function to convert class="type">uchar array to class="type">class="kw">string                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CharArrayToString(class="kw">const class="type">uchar &arr[], class="type">int start, class="type">int length)
 {
 class="type">class="kw">string result;
 class="type">char temp[];
 ArrayResize(temp, length);
 ArrayCopy(temp, arr, class="num">0, start, length);
 result = CharArrayToString(temp);
 class="kw">return result;
}
class=class="str">"cmt">//+------------------------------------------------------------------+

用本地套接字把推特情绪喂给 MT5

这套桥接思路的核心是在本机起一个 TCP 服务器,MT5 EA 通过 socket 把交易品种和 Twitter 凭证发过来,Python 侧抓推文做情绪打分再回传。下面的 start_server 绑定 127.0.0.1:65432,只监听 1 个连接,属于典型的本机低延迟方案,不涉及跨网络暴露。 代码里 recv 缓冲区设为 1024 字节,收到后用 utf-8 解码并忽略错误,再按逗号切分。它硬性要求 inputs 长度等于 8,否则直接抛 ValueError——这意味着 EA 端每次必须规整地传满 8 个字段(含 symbol 与各类 token),少一个就走异常处理分支。 analyze_tweets 用 Bearer Token 走 Twitter API,查询串拼成「{symbol} lang:en -is:retweet」,即只取英文非转推。TextBlob 对单条打情绪分后取均值,这个均值就是回传给 EA 的 tweet_sentiment,EA 据此倾向在正值开买、负值开卖。 别把凭证明文当小事 当前实现每次请求都把 API 密钥在 socket 里明文往返。本机回环还好,若哪天改成远程部署,这套写法会直接泄露凭证,生产环境必须上环境变量或鉴权层。 每 30 分钟抓一批最新推文,系统提供的是近似实时的情绪流,但现版只开仓不跟情绪反转调仓。外汇与贵金属波动受多因子驱动,纯推特情绪信号误触发概率不低,上真实账户前建议在 MT5 策略测试器跑至少三个月样本外验证。

MQL5 / C++
def start_server():
    """Starts the server that waits for incoming connections."""
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((&class="macro">#x27;class="num">127.0.class="num">0.1&class="macro">#x27;, class="num">65432))
    server_socket.listen(class="num">1)
    print("Python server started and waiting for connections...")
    while True:
        client_socket, addr = server_socket.accept()
        print(f"Connection from {addr}")
        
        try:
            data = client_socket.recv(class="num">1024)
            data = data.decode(&class="macro">#x27;utf-class="num">8&class="macro">#x27;, errors=&class="macro">#x27;ignore&class="macro">#x27;)
            print(f"Received data: {data}")
            inputs = data.split(&class="macro">#x27;,&class="macro">#x27;)
            if len(inputs) != class="num">8:
                raise ValueError("Eight inputs were expected")
            symbol, twitter_api_key, twitter_api_secret, twitter_access_token, twitter_access_token_secret, twitter_bearer_token, client_id, client_secret = inputs
            result = process_data(symbol, twitter_bearer_token)
            result_string = f"{result[&class="macro">#x27;tweet_sentiment&class="macro">#x27;]}"
            client_socket.sendall(result_string.encode(&class="macro">#x27;utf-class="num">8&class="macro">#x27;))
            print(f"Response sent to client: {result_string}")
        except Exception as e:
            print(f"Communication error: {e}")
            error_message = f"ERROR,{str(e)}"
            client_socket.sendall(error_message.encode(&class="macro">#x27;utf-class="num">8&class="macro">#x27;))
        finally:
            client_socket.shutdown(socket.SHUT_RDWR)
            client_socket.close()
            print("Connection closed")

def process_data(symbol, bearer_token):
    """Processes the data obtained from news and tweets."""
    result = { "tweet_sentiment": class="num">0}
    try:
        result["tweet_sentiment"] = analyze_tweets(bearer_token, symbol)
    except Exception as e:
        raise Exception(f"Error processing data: {e}")
    
    print(f"Data processed. Result: {result}")
    class="kw">return result

def analyze_tweets(bearer_token, symbol):
    """Analyzes recent tweets related to the given symbol."""
    try:
        headers = {
            &class="macro">#x27;Authorization&class="macro">#x27;: f&class="macro">#x27;Bearer {bearer_token}&class="macro">#x27;,
        }
        query = f"{symbol} lang:en -is:retweet"
        # Get the current time and subtract an hour

◍ 用 Twitter 近因情绪给行情打辅助分

这段 Python 片段演示了如何拉取最近 4 小时、剔除当前 10 秒噪点的推文,再做情感极性均值。时间窗设为 end_time 减 10 秒、再往前推 4 小时,转成 RFC3339 的 'Z' 格式,直接喂给 Twitter v2 recent search 接口,max_results 锁 100 条、按相关度排序。 请求回来先判 status_code,不是 200 就抛异常并打印原文;拿到 data 列表后若为空,函数直接 return 0,意味着这段窗口里没有可计算样本。 sentiments 用 TextBlob 对每条 tweet['text'] 算 polarity,空列表同样回 0。最终 average_sentiment = sum/len,值域约在 [-1,1],负数倾向恐慌、正数倾向乐观。外汇与贵金属受社交情绪扰动明显,这类信号仅作概率参考,实盘仍属高风险。 把平均极性接进 MT5 的自定义指标或 EA,可在欧美盘重叠时段观察:当均值低于 -0.3 且黄金 15 分钟出现长下影,反弹概率可能抬升,但是否入场还得看价位结构。

MQL5 / C++
    end_time = class="type">class="kw">datetime.now(timezone.utc) - timedelta(seconds=class="num">10)  # Subtract class="num">10 seconds from the current time
    start_time = end_time - timedelta(hours=class="num">4)
    # Convert to RFC class="num">3339 (ISO class="num">8601) format with second precision and &class="macro">#x27;Z&class="macro">#x27; at the end
    start_time_str = start_time.strftime(&class="macro">#x27;%Y-%m-%dT%H:%M:%SZ&class="macro">#x27;)
    end_time_str = end_time.strftime(&class="macro">#x27;%Y-%m-%dT%H:%M:%SZ&class="macro">#x27;)
    search_url = f"https:class=class="str">"cmt">//api.twitter.com/class="num">2/tweets/search/recent?query={query}&max_results=class="num">100&start_time={start_time_str}&end_time={end_time_str}&sort_order=relevancy"
    
    print(f"Performing tweet search with query: {query}")
    print(f"Search URL: {search_url}")
    response = requests.get(search_url, headers=headers)
    print(f"Response status code: {response.status_code}")
    print(f"Response text: {response.text}")
    if response.status_code != class="num">200:
        raise Exception(f"Error searching tweets: {response.status_code} - {response.text}")
    tweets = response.json().get(&class="macro">#x27;data&class="macro">#x27;, [])
    if not tweets:
        print("No tweets found")
        class="kw">return class="num">0
    
    sentiments = [TextBlob(tweet[&class="macro">#x27;text&class="macro">#x27;]).sentiment.polarity for tweet in tweets]
    if not sentiments:
        print("No sentiments found")
        class="kw">return class="num">0
    
    average_sentiment = sum(sentiments) / len(sentiments)
    class="kw">return average_sentiment
    except Exception as e:
        print(f"Error: {e}")
        raise Exception(f"Error analyzing tweets: {e}")
让小布替你盯 socket 心跳
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到EA与Python服务端的通联状态,省去你手动抓包看端口的重复劳动。

常见问题

优先确认服务端监听端口已绑定且未被防火墙拦截,MT5客户端允许网络访问,以及OnInit中服务器地址与端口字符串格式无误,三者任一异常都会导致连接返回无效句柄。
通常通过记录上次发送时间的全局变量与TimeCurrent差值判断,达到1800秒阈值才调用CommunicateWithPython,避免每个tick都建立socket造成资源耗尽。
目前小布盯盘内置的是品种维度的替代数据热度与通联诊断,原始Twitter评分需你自己的Python服务端算出后回传,小布只负责把断链和异常态浮出来。
概率上这类二元映射在弱趋势品种易触发噪音交易,实盘可能偏向过滤极低绝对值评分或叠加波动率门槛,外汇与贵金属属高风险市场须自行回测。