Пример #1
0
 def _md_recv(self, pack):
     for d in pack["data"]:
         self._diffs.append(d)
         # 在第一次收到 mdhis_more_data 为 False 的时候,发送账户初始截面信息,这样回测模式下,往后的模块才有正确的时间顺序
         if not self._has_send_init_account and not d.get(
                 "mdhis_more_data", True):
             self._diffs.append(self._sim_trade.init_snapshot())
             self._diffs.append(
                 {"trade": {
                     self._account_key: {
                         "trade_more_data": False
                     }
                 }})
             self._has_send_init_account = True
         _tqsdk_backtest = d.get("_tqsdk_backtest", {})
         if _tqsdk_backtest:
             # 回测时,用 _tqsdk_backtest 对象中 current_dt 作为 TqSim 的 _current_datetime
             self._tqsdk_backtest.update(_tqsdk_backtest)
             self._current_datetime = _timestamp_nano_to_str(
                 self._tqsdk_backtest["current_dt"])
             self._local_time_record = float("nan")
             # 1. 回测时不使用时间差来模拟交易所时间的原因(_local_time_record始终为初始值nan):
             #   在sim收到行情后记录_local_time_record,然后下发行情到api进行merge_diff(),api需要处理完k线和quote才能结束wait_update(),
             #   若处理时间过长,此时下单则在判断下单时间时与测试用例中的预期时间相差较大,导致测试用例无法通过。
             # 2. 回测不使用时间差的方法来判断下单时间仍是可行的: 与使用了时间差的方法相比, 只对在每个交易时间段最后一笔行情时的下单时间判断有差异,
             #   若不使用时间差, 则在最后一笔行情时下单仍判断为在可交易时间段内, 且可成交.
         quotes_diff = d.get("quotes", {})
         # 先根据 quotes_diff 里的 datetime, 确定出 _current_datetime,再 _merge_diff(同时会发送行情到 quote_chan)
         for symbol, quote_diff in quotes_diff.items():
             if quote_diff is None:
                 continue
             # 若直接使用本地时间来判断下单时间是否在可交易时间段内 可能有较大误差,因此判断的方案为:(在接收到下单指令时判断 估计的交易所时间 是否在交易时间段内)
             # 在更新最新行情时间(即self._current_datetime)时,记录当前本地时间(self._local_time_record),
             # 在这之后若收到下单指令,则获取当前本地时间,判 "最新行情时间 + (当前本地时间 - 记录的本地时间)" 是否在交易时间段内。
             # 另外, 若在盘后下单且下单前未订阅此合约:
             # 因为从_md_recv()中获取数据后立即判断下单时间则速度过快(两次time.time()的时间差小于最后一笔行情(14:59:9995)到15点的时间差),
             # 则会立即成交,为处理此情况则将当前时间减去5毫秒(模拟发生5毫秒网络延迟,则两次time.time()的时间差增加了5毫秒)。
             # todo: 按交易所来存储 _current_datetime(issue: #277)
             if quote_diff.get("datetime", "") > self._current_datetime:
                 # 回测时,当前时间更新即可以由 quote 行情更新,也可以由 _tqsdk_backtest.current_dt 更新,
                 # 在最外层的循环里,_tqsdk_backtest.current_dt 是在 rtn_data.data 中数组位置中的最后一个,会在循环最后一个才更新 self.current_datetime
                 # 导致前面处理 order 时的 _current_datetime 还是旧的行情时间
                 self._current_datetime = quote_diff["datetime"]  # 最新行情时间
                 # 更新最新行情时间时的本地时间,回测时不使用时间差
                 self._local_time_record = (
                     time.time() -
                     0.005) if not self._tqsdk_backtest else float("nan")
             if self._current_datetime > self._trading_day_end:  # 结算
                 self._settle()
                 # 若当前行情时间大于交易日的结束时间(切换交易日),则根据此行情时间更新交易日及交易日结束时间
                 trading_day = _get_trading_day_from_timestamp(
                     self._get_current_timestamp())
                 self._trading_day_end = _timestamp_nano_to_str(
                     _get_trading_day_end_time(trading_day) - 999)
         if quotes_diff:
             _merge_diff(self._data, {"quotes": quotes_diff},
                         self._prototype,
                         persist=False,
                         reduce_diff=False,
                         notify_update_diff=True)
Пример #2
0
    async def _md_recv(self, pack):
        """ 处理下行数据包
        0 将行情数据和交易数据合并至 self._data
        1 生成增量业务截面, 该截面包含期权补充的字段
        """
        for d in pack.get("data", {}):
            self._datetime_state.update_state(d)
            _simple_merge_diff(self._data_quotes, d.get('quotes', {}))
            _merge_diff(self._data, {"trade": d.get('trade', {})}, prototype=self._prototype, persist=False, reduce_diff=False)
            self._diffs.append(d)  # 添加至 self._diff 等待被发送

        for obj in self._new_objs_list:
            # 新添加的 Position / Order / Trade  节点
            if hasattr(obj, '_path') and obj['_path'][2] in ['positions', 'trades', 'orders']:
                symbol = f"{obj.get('exchange_id', '')}.{obj.get('instrument_id', '')}"
                if symbol not in self._all_trade_symbols:
                    self._all_trade_symbols.add(symbol)
                    self._need_wait_symbol_info.add(symbol)  # 需要发送合约信息请求

        for s in self._need_wait_symbol_info.copy():
            if self._data_quotes.get(s, {}).get("price_tick", 0) > 0:
                self._need_wait_symbol_info.remove(s)  # 需要发送合约信息请求 + 不知道合约信息的合约

        # 不知道合约信息 并且未发送请求查询合约信息
        unknown_symbols = self._need_wait_symbol_info - self._query_symbols
        if len(unknown_symbols) > 0:
            self._query_symbols = self._query_symbols.union(unknown_symbols)  # 所有发送过ins_query的合约
            query_pack = _query_for_quote(list(unknown_symbols))
            await self._md_send_chan.send(query_pack)
Пример #3
0
 async def _run(self, api, api_send_chan, api_recv_chan, ws_send_chan, ws_recv_chan):
     self._api = api
     send_task = self._api.create_task(self._send_handler(api_send_chan, ws_send_chan))
     try:
         async for pack in ws_recv_chan:
             self._record_upper_data(pack)
             if self._un_processed:  # 处理重连后数据
                 pack_data = pack.get("data", [])
                 self._pending_diffs.extend(pack_data)
                 for d in pack_data:
                     # _merge_diff 之后, self._data 会用于判断是否接收到了完整截面数据
                     _merge_diff(self._data, d, self._api._prototype, persist=False, reduce_diff=False)
                 if self._is_all_received():
                     # 重连后收到完整数据截面
                     self._un_processed = False
                     pack = {
                         "aid": "rtn_data",
                         "data": self._pending_diffs
                     }
                     await api_recv_chan.send(pack)
                     self._logger = self._logger.bind(status=self._status)
                     self._logger.debug("data completed", pack=pack)
                 else:
                     await ws_send_chan.send({"aid": "peek_message"})
                     self._logger.debug("wait for data completed", pack={"aid": "peek_message"})
             else:
                 is_reconnected = False
                 for i in range(len(pack.get("data", []))):
                     for _, notify in pack["data"][i].get("notify", {}).items():
                         if notify["code"] == 2019112902:  # 重连建立
                             is_reconnected = True
                             self._un_processed = True
                             self._logger = self._logger.bind(status=self._status)
                             if i > 0:
                                 ws_send_chan.send_nowait({
                                     "aid": "rtn_data",
                                     "data": pack.get("data", [])[0:i]
                                 })
                             self._pending_diffs = pack.get("data", [])[i:]
                             break
                 if is_reconnected:
                     self._data = Entity()
                     self._data._instance_entity([])
                     for d in self._pending_diffs:
                         _merge_diff(self._data, d, self._api._prototype, persist=False, reduce_diff=False)
                     # 发送所有 resend_request
                     for msg in self._resend_request.values():
                         # 这里必须用 send_nowait 而不是 send,因为如果使用异步写法,在循环中,代码可能执行到 send_task, 可能会修改 _resend_request
                         ws_send_chan.send_nowait(msg)
                         self._logger.debug("resend request", pack=msg)
                     await ws_send_chan.send({"aid": "peek_message"})
                 else:
                     await api_recv_chan.send(pack)
     finally:
         send_task.cancel()
         await asyncio.gather(send_task, return_exceptions=True)
Пример #4
0
    async def _md_handler(self):
        async for pack in self._md_recv_chan:
            await self._md_send_chan.send({
                "aid": "peek_message"
            })
            for d in pack.get("data", []):
                _merge_diff(self._data, d, self._prototype, False)

                # 收到的 quotes 转发给下游
                quotes = d.get("quotes", {})
                if quotes:
                    quotes = self._update_valid_quotes(quotes)  # 删去回测 quotes 不应该下发的字段
                    self._diffs.append({"quotes": quotes})

                # 收到的 symbols 应该转发给下游
                if d.get("symbols"):
                    self._diffs.append({"symbols": d["symbols"]})
Пример #5
0
 async def _md_handler(self):
     async for pack in self._md_recv_chan:
         await self._md_send_chan.send({
             "aid": "peek_message"
         })
         recv_quotes = False
         for d in pack.get("data", []):
             _merge_diff(self._data, d, self._prototype, persist=False, reduce_diff=False)
             # 收到的 quotes 转发给下游
             quotes = d.get("quotes", {})
             if quotes:
                 recv_quotes = True
                 quotes = self._update_valid_quotes(quotes)  # 删去回测 quotes 不应该下发的字段
                 self._diffs.append({"quotes": quotes})
             # 收到的 symbols 应该转发给下游
             if d.get("symbols"):
                 self._diffs.append({"symbols": d["symbols"]})
         # 如果没有收到 quotes(合约信息),或者当前的 self._data.get('quotes', {}) 里没有股票,那么不应该向 _diffs 里添加元素
         if recv_quotes:
             quotes_stock = self._stock_dividend._get_dividend(self._data.get('quotes', {}), self._trading_day)
             if quotes_stock:
                 self._diffs.append({"quotes": quotes_stock})
Пример #6
0
 async def _md_handler(self):
     async for pack in self._md_recv_chan:
         await self._md_send_chan.send({"aid": "peek_message"})
         for d in pack.get("data", []):
             _merge_diff(self._data, d, self._api._prototype, False)