Repository: vnpy/vnpy

Issue:
CTP:缺少ExchangeID字段，请填入ExchangeID
## 环境

* 操作系统: 如Windows 10
* vn.py版本: 如v1.9

## Issue类型
三选一：Bug/Enhancement

## 实际程序行为
ctp发单交易报错，错误信息：
CTP:缺少ExchangeID字段，请填入ExchangeID


## 重现步骤
simnow测试交易即报错，接口修改了，具体信息如下：

【终端开发重要通知】TradeAPI使用微调
各位终端开发商好！
在CTP期货生产交易系统V7.0.0版本及之后版本（预计3~5个月内发布，目前生产最新版本为CTP V6.3.17_P4_20190628），客户端在使用tradeapi时，业务指令中有InvestorID和exchangeID的域都必须填写，之前没有填写的后台将会返回错误。其中：
1）交易所编号ExchangeID全部大写CFFEX、CZCE、DCE、INE、SHFE；
2）此处的业务类指令指交易类，不是查询类；换言之查询类接口无须变动；
3）现有官网TradeAPI都已支持；
4）7月中旬左右，SimNow网站提供Internet测试环境，具体时间请关注本群。
特此提前通知，请各位开发商务必仔细核查系统是否有遗漏或没有填写，并安排开发计划进行修改。


错误截图：
![image](https://user-images.githubusercontent.com/31505150/60930697-36ae8f00-a2e9-11e9-9129-cf79ce234d5f.png)





Discussion:
@vnpy 目前测试环境都没法测试了。
我报的不是这个错，报的是“CTP：客户端认证失败”，也不知道是不是一个原因
在ctpGateway.py的sendOrder函数里找个地方加这一句就可以了：
req['ExchangeID'] = orderReq.exchange.upper()

Resolving patch:
diff --git a/vnpy/api/websocket/websocket_client.py b/vnpy/api/websocket/websocket_client.py
--- a/vnpy/api/websocket/websocket_client.py
+++ b/vnpy/api/websocket/websocket_client.py
@@ -125,52 +125,55 @@ def _send_binary(self, data: bytes):
         if ws:
             ws._send_binary(data)
 
-    def _reconnect(self):
-        """"""
-        if self._active:
-            self._disconnect()
-            self._connect()
-
     def _create_connection(self, *args, **kwargs):
         """"""
         return websocket.create_connection(*args, **kwargs)
 
-    def _connect(self):
+    def _ensure_connection(self):
         """"""
-        self._ws = self._create_connection(
-            self.host,
-            sslopt={"cert_reqs": ssl.CERT_NONE},
-            http_proxy_host=self.proxy_host,
-            http_proxy_port=self.proxy_port,
-            header=self.header
-        )
-        self.on_connected()
+        triggered = False
+        with self._ws_lock:
+            if self._ws is None:
+                self._ws = self._create_connection(
+                    self.host,
+                    sslopt={"cert_reqs": ssl.CERT_NONE},
+                    http_proxy_host=self.proxy_host,
+                    http_proxy_port=self.proxy_port,
+                    header=self.header
+                )
+                triggered = True
+        if triggered:
+            self.on_connected()
 
     def _disconnect(self):
         """
         """
+        triggered = False
         with self._ws_lock:
             if self._ws:
-                self._ws.close()
+                ws: websocket.WebSocket = self._ws
                 self._ws = None
 
+                triggered = True
+        if triggered:
+            ws.close()
+            self.on_disconnected()
+
     def _run(self):
         """
         Keep running till stop is called.
         """
         try:
-            self._connect()
-
-            # todo: onDisconnect
             while self._active:
                 try:
+                    self._ensure_connection()
                     ws = self._ws
                     if ws:
                         text = ws.recv()
 
                         # ws object is closed when recv function is blocking
                         if not text:
-                            self._reconnect()
+                            self._disconnect()
                             continue
 
                         self._record_last_received_text(text)
@@ -185,17 +188,17 @@ def _run(self):
                 # ws is closed before recv function is called
                 # For socket.error, see Issue #1608
                 except (websocket.WebSocketConnectionClosedException, socket.error):
-                    self._reconnect()
+                    self._disconnect()
 
                 # other internal exception raised in on_packet
                 except:  # noqa
                     et, ev, tb = sys.exc_info()
                     self.on_error(et, ev, tb)
-                    self._reconnect()
+                    self._disconnect()
         except:  # noqa
             et, ev, tb = sys.exc_info()
             self.on_error(et, ev, tb)
-            self._reconnect()
+        self._disconnect()
 
     @staticmethod
     def unpack_data(data: str):
@@ -214,7 +217,10 @@ def _run_ping(self):
             except:  # noqa
                 et, ev, tb = sys.exc_info()
                 self.on_error(et, ev, tb)
-                self._reconnect()
+
+                # self._run() will reconnect websocket
+                sleep(1)
+
             for i in range(self.ping_interval):
                 if not self._active:
                     break
diff --git a/vnpy/gateway/ctp/ctp_gateway.py b/vnpy/gateway/ctp/ctp_gateway.py
--- a/vnpy/gateway/ctp/ctp_gateway.py
+++ b/vnpy/gateway/ctp/ctp_gateway.py
@@ -716,6 +716,7 @@ def send_order(self, req: OrderRequest):
         
         ctp_req = {
             "InstrumentID": req.symbol,
+            "ExchangeID": req.exchange.value,
             "LimitPrice": req.price,
             "VolumeTotalOriginal": int(req.volume),
             "OrderPriceType": ORDERTYPE_VT2CTP.get(req.type, ""),


Test patch: