import zmq
import json
import time
import threading

class ZeroMQClient:
    def __init__(self, pub_port=5555, req_port=5556):
        self.context = zmq.Context()
        
        # PUB/SUB 通道：用于接收 MT5 的高频行情广播
        self.sub_socket = self.context.socket(zmq.SUB)
        self.sub_socket.connect(f"tcp://127.0.0.1:{pub_port}")
        self.sub_socket.setsockopt_string(zmq.SUBSCRIBE, "")  # 订阅所有主题
        
        # REQ/REP 通道：用于向 MT5 发送交易指令并同步等待结果
        self.req_socket = self.context.socket(zmq.REQ)
        self.req_socket.connect(f"tcp://127.0.0.1:{req_port}")
        
        self.latest_tick = None
        self.running = True
        
        # 启动后台线程持续监听行情
        self.listener_thread = threading.Thread(target=self._listen_ticks, daemon=True)
        self.listener_thread.start()

    def _listen_ticks(self):
        """后台持续接收 MT5 广播的 Tick 数据"""
        while self.running:
            try:
                # 使用 NOBLOCK 防止阻塞退出
                msg = self.sub_socket.recv_string(flags=zmq.NOBLOCK)
                try:
                    data = json.loads(msg)
                    self.latest_tick = data
                    # print(f"Received tick: {data}")
                except json.JSONDecodeError:
                    pass
            except zmq.Again:
                time.sleep(0.001)  # 1ms 轮询
            except Exception as e:
                print(f"ZMQ SUB error: {e}")
                time.sleep(1)

    def get_latest_price(self):
        """提供给主程序的接口：获取最新行情"""
        if self.latest_tick:
            return self.latest_tick.get("bid"), self.latest_tick.get("ask"), self.latest_tick.get("spread")
        return None, None, None

    def send_order(self, action, volume, sl=0, tp=0):
        """提供给主程序的接口：发送交易指令"""
        req_data = {
            "action": action,
            "volume": volume,
            "sl": sl,
            "tp": tp,
            "magic": 999
        }
        try:
            # 1. 发送指令
            self.req_socket.send_string(json.dumps(req_data))
            
            # 2. 等待 EA 执行结果 (设置超时防止死锁)
            # 在实际生产中，可以使用 Poller 来实现带超时的 recv
            poller = zmq.Poller()
            poller.register(self.req_socket, zmq.POLLIN)
            socks = dict(poller.poll(5000)) # 5秒超时
            
            if self.req_socket in socks:
                reply = self.req_socket.recv_string()
                return json.loads(reply)
            else:
                # 超时需要重置 socket 状态
                self.req_socket.setsockopt(zmq.LINGER, 0)
                self.req_socket.close()
                poller.unregister(self.req_socket)
                self.req_socket = self.context.socket(zmq.REQ)
                self.req_socket.connect(f"tcp://127.0.0.1:5556")
                return {"status": "TIMEOUT", "message": "MT5 not responding"}
                
        except Exception as e:
            return {"status": "ERROR", "message": str(e)}

    def close(self):
        self.running = False
        self.sub_socket.close()
        self.req_socket.close()
        self.context.term()

if __name__ == "__main__":
    # 测试代码
    client = ZeroMQClient()
    print("ZeroMQ Client started. Waiting for ticks from MT5...")
    try:
        while True:
            bid, ask, spr = client.get_latest_price()
            if bid:
                print(f"Current Price -> Bid: {bid}, Ask: {ask}, Spread: {spr}")
            time.sleep(1)
    except KeyboardInterrupt:
        client.close()
