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)
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)
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)
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"]})
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})
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)