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 _quote_handler(self, symbol, quote_chan, order_chan): try: orders = self._orders.setdefault(symbol, {}) position = self._normalize_position(symbol) self._positions[symbol] = position await self._subscribe_quote(symbol) quote = await self._ensure_quote(symbol, quote_chan) underlying_quote = None if quote["ins_class"] in ["FUTURE_OPTION", "OPTION"]: # 如果是期权,订阅标的合约行情,确定收到期权标的合约行情 underlying_symbol = quote["underlying_symbol"] await self._subscribe_quote(underlying_symbol) underlying_quote = await self._ensure_quote(underlying_symbol, quote_chan) # 订阅合约 # 在等待标的行情的过程中,quote_chan 可能有期权行情,把 quote_chan 清空,并用最新行情更新 quote while not quote_chan.empty(): quote_chan.recv_nowait() quote_chan.task_done() quote.update(self._data["quotes"][symbol]) if underlying_quote: underlying_quote.update(self._data["quotes"][underlying_symbol]) task = self._api.create_task(self._forward_chan_handler(order_chan, quote_chan)) async for pack in quote_chan: if "aid" not in pack: _simple_merge_diff(quote, pack.get("quotes", {}).get(symbol, {})) if underlying_quote: _simple_merge_diff(underlying_quote, pack.get("quotes", {}).get(underlying_symbol, {})) for order_id in list(orders.keys()): assert orders[order_id]["insert_date_time"] > 0 match_msg = self._match_order(orders[order_id], symbol, quote, underlying_quote) if match_msg: self._del_order(symbol, orders[order_id], match_msg) del orders[order_id] self._adjust_position(symbol, price=quote["last_price"]) # 按照合约最新价调整持仓 elif pack["aid"] == "insert_order": order = self._normalize_order(pack) # 调整 pack 为 order 对象需要的字段 orders[pack["order_id"]] = order # 记录 order 在 orders 里 insert_result = self._insert_order(order, symbol, quote, underlying_quote) if insert_result: self._del_order(symbol, order, insert_result) del orders[pack["order_id"]] else: match_msg = self._match_order(order, symbol, quote, underlying_quote) if match_msg: self._del_order(symbol, orders[pack["order_id"]], match_msg) del orders[pack["order_id"]] # 按照合约最新价调整持仓 self._adjust_position(symbol, price=quote["last_price"]) await self._send_diff() elif pack["aid"] == "cancel_order": if pack["order_id"] in orders: self._del_order(symbol, orders[pack["order_id"]], "已撤单") del orders[pack["order_id"]] await self._send_diff() quote_chan.task_done() finally: await quote_chan.close() await order_chan.close() task.cancel() await asyncio.gather(task, return_exceptions=True)
def _generate_ext_diff(self): """" 补充 quote, position 额外字段 此函数在 send_diff() 才会调用, self._datetime_state.data_ready 一定为 True, 调用 self._datetime_state.get_current_dt() 一定有正确的当前时间 """ for d in self._diffs: if d.get('quotes', None): self._update_quotes(d) pend_diff = {} _simple_merge_diff(pend_diff, self._get_positions_pend_diff()) orders_set = set() # 计算过委托单,is_dead、is_online、is_error orders_price_set = set() # 根据成交计算哪些 order 需要重新计算平均成交价 trade_price for path in self._diffs_paths: if path[2] == 'orders': _, account_key, _, order_id, _ = path if (account_key, order_id) not in orders_set: orders_set.add((account_key, order_id)) order = _get_obj( self._data, ['trade', account_key, 'orders', order_id]) if order: pend_order = pend_diff.setdefault( 'trade', {}).setdefault(account_key, {}).setdefault( 'orders', {}).setdefault(order_id, {}) pend_order['is_dead'] = order['status'] == "FINISHED" pend_order['is_online'] = order[ 'exchange_order_id'] != "" and order[ 'status'] == "ALIVE" pend_order['is_error'] = order[ 'exchange_order_id'] == "" and order[ 'status'] == "FINISHED" elif path[2] == 'trades': _, account_key, _, trade_id = path trade = _get_obj(self._data, path) order_id = trade.get('order_id', '') if order_id: orders_price_set.add( ('trade', account_key, 'orders', order_id)) for path in orders_price_set: _, account_key, _, order_id = path trade_price = self._get_trade_price(account_key, order_id) if trade_price == trade_price: pend_order = pend_diff.setdefault('trade', {}).setdefault( account_key, {}).setdefault('orders', {}).setdefault(order_id, {}) pend_order['trade_price'] = trade_price self._diffs_paths = set() return pend_diff
async def _generate_pend_diff(self): """" 盈亏计算 """ pend_diff = {} pend_diff.setdefault( 'trade', { k: { 'accounts': { 'CNY': {} }, 'positions': {} } for k in self._data.get('trade', {}) }) # 计算持仓盈亏 for account_key in self._data.get('trade', {}): # 盈亏计算仅仅计算股票账户 if self._data['trade'].get(account_key, {}).get("account_type", "FUTURE") == "FUTURE": continue for symbol, _ in self._data['trade'][account_key].get( 'positions', {}).items(): await self._subscribe_quote(symbol) last_price = self._data["quotes"].get(symbol, {}).get( 'last_price', float("nan")) if not math.isnan(last_price): diff = self._update_position(account_key, symbol, last_price) pend_diff['trade'][account_key]['positions'][symbol] = diff _simple_merge_diff( self._data["trade"][account_key]["positions"], {symbol: diff}) # 当截面完整时, 全量刷新所有账户的资产盈亏 if self._is_diff_complete(): for account_key in self._data.get('trade', {}): if self._data['trade'].get(account_key, {}).get("account_type", "FUTURE") == "FUTURE": continue all_position = self._data["trade"][account_key].get( "positions", {}) pend_diff['trade'][account_key]['accounts']['CNY']['float_profit'] = \ sum([v.get('float_profit', 0) for k, v in all_position.items()]) return pend_diff
def update_quotes(self, symbol, pack): for q in pack.get("quotes", {}).values(): self._max_datetime = max(q.get("datetime", ""), self._max_datetime) _simple_merge_diff(self._quotes, pack.get("quotes", {})) quote, underlying_quote = self._get_quotes_by_symbol(symbol) # 某些非交易时间段,ticks 回测是 quote 的最新价有可能是 nan,无效的行情直接跳过 if math.isnan(quote["last_price"]): return [], [] # 撮合委托单 orders = self._orders.get(symbol, {}) position = self._ensure_position(symbol, quote, underlying_quote) for order_id in list(orders.keys()): # match_order 过程中可能会删除 orders 下对象 self._match_order(orders[order_id], symbol, position, quote, underlying_quote) self._on_update_quotes(symbol, position, quote, underlying_quote) # 调整持仓及账户信息 return self._return_results()
async def _md_recv(self, pack): """ 处理下行数据包 0 将行情数据和交易数据合并至 self._data 1 生成增量业务截面, 该截面包含 持仓盈亏和资产盈亏信息 """ for d in pack.get("data", {}): if "quotes" in d: # 行情数据仅仅合并沪深两市的行情数据 stock_quote = { k: v for k, v in d.get('quotes').items() if k.startswith("SSE") or k.startswith("SZSE") } _simple_merge_diff(self._data, {"quotes": stock_quote}) if "trade" in d: _simple_merge_diff(self._data, d) # 添加至 self._diff 等待被发送 self._diffs.append(d) # 计算持仓和账户资产的盈亏增量截面 pend_diff = await self._generate_pend_diff() self._diffs.append(pend_diff)
async def _data_handler(self, api_recv_chan, web_recv_chan): async for pack in web_recv_chan: if pack['aid'] == 'rtn_data': web_diffs = [] account_changed = False for d in pack['data']: # 把 d 处理成需要的数据 # 处理 trade trade = d.get("trade") if trade is not None: _simple_merge_diff(self._data["trade"], trade) web_diffs.append({"trade": trade}) # 账户是否有变化 static_balance_changed = d.get("trade", {}).get(self._api._account._account_id, {}).\ get("accounts", {}).get("CNY", {}).get('static_balance') trades_changed = d.get("trade", {}).get( self._api._account._account_id, {}).get("trades", {}) orders_changed = d.get("trade", {}).get( self._api._account._account_id, {}).get("orders", {}) if static_balance_changed is not None or trades_changed != {} or orders_changed != {}: account_changed = True # 处理 backtest replay if d.get("_tqsdk_backtest") or d.get("_tqsdk_replay"): _simple_merge_diff(self._data, d) web_diffs.append(d) # 处理通知,行情和交易连接的状态 notify_diffs = self._notify_handler(d.get("notify", {})) for diff in notify_diffs: _simple_merge_diff(self._data, diff) web_diffs.extend(notify_diffs) if account_changed: dt, snapshot = self.get_snapshot() _snapshots = {"snapshots": {}} _snapshots["snapshots"][dt] = snapshot web_diffs.append(_snapshots) _simple_merge_diff(self._data, _snapshots) for chan in self._conn_diff_chans: self.send_to_conn_chan(chan, web_diffs) # 接收的数据转发给下游 api await api_recv_chan.send(pack)
async def _data_handler(self, api_recv_chan, web_recv_chan): async for pack in web_recv_chan: if pack['aid'] == 'rtn_data': web_diffs = [] account_changed = False for d in pack['data']: # 把 d 处理成需要的数据 # 处理 trade trade = d.get("trade") if trade is not None: _simple_merge_diff(self._data["trade"], trade) web_diffs.append({"trade": trade}) # 账户是否有变化 # todo: 这里本来使用类似 is_changing 的做法,判断 diffs 中是否有 static_balance 字段;由于 _simple_merge_diff 去掉了默认参数 reduce_diff # 现在修改为手动比较 diffs 中的 static_balance 和 self._data 中的 static_balance 是否一样 account_key = self._api._account._get_account_key(account=None) current_static_balance = self._data["trade"].get(account_key, {}).get("accounts", {}).get("CNY", {}).get('static_balance') diff_static_balance = d.get("trade", {}).get(account_key, {}).get("accounts", {}).get("CNY", {}).get('static_balance', None) static_balance_changed = diff_static_balance is not None and current_static_balance != diff_static_balance trades_changed = d.get("trade", {}).get(account_key, {}).get("trades", {}) orders_changed = d.get("trade", {}).get(account_key, {}).get("orders", {}) if static_balance_changed is True or trades_changed != {} or orders_changed != {}: account_changed = True # 处理 backtest replay if d.get("_tqsdk_backtest") or d.get("_tqsdk_replay"): _simple_merge_diff(self._data, d) web_diffs.append(d) # 处理通知,行情和交易连接的状态 notify_diffs = self._notify_handler(d.get("notify", {})) for diff in notify_diffs: _simple_merge_diff(self._data, diff) web_diffs.extend(notify_diffs) if account_changed: dt, snapshot = self.get_snapshot() _snapshots = {"snapshots": {}} _snapshots["snapshots"][dt] = snapshot web_diffs.append(_snapshots) _simple_merge_diff(self._data, _snapshots) for chan in self._conn_diff_chans: self.send_to_conn_chan(chan, web_diffs) # 接收的数据转发给下游 api await api_recv_chan.send(pack)
async def _run(self, api_send_chan, api_recv_chan, web_send_chan, web_recv_chan): if not self._api._web_gui: # 没有开启 web_gui 功能 _data_handler_without_web_task = self._api.create_task( self._data_handler_without_web(api_recv_chan, web_recv_chan)) try: async for pack in api_send_chan: # api 发送的包,过滤出 set_chart_data, 其余的原样转发 if pack['aid'] != 'set_chart_data': await web_send_chan.send(pack) finally: _data_handler_without_web_task.cancel() await asyncio.gather(_data_handler_without_web_task, return_exceptions=True) else: self._web_dir = os.path.join(os.path.dirname(__file__), 'web') file_path = os.path.abspath(sys.argv[0]) file_name = os.path.basename(file_path) # 初始化数据截面 self._data = { "action": { "mode": "replay" if isinstance(self._api._backtest, TqReplay) else "backtest" if isinstance(self._api._backtest, TqBacktest) else "run", "md_url_status": '-', "td_url_status": True if isinstance(self._api._account, TqSim) else '-', "account_id": self._api._account._account_id, "broker_id": self._api._account._broker_id if isinstance( self._api._account, TqAccount) else 'TQSIM', "file_path": file_path[0].upper() + file_path[1:], "file_name": file_name }, "trade": {}, "subscribed": [], "draw_chart_datas": {}, "snapshots": {} } self._order_symbols = set() self._diffs = [] self._conn_diff_chans = set() _data_task = self._api.create_task( self._data_handler(api_recv_chan, web_recv_chan)) _httpserver_task = self._api.create_task(self.link_httpserver()) try: # api 发送的包,过滤出需要的包记录在 self._data async for pack in api_send_chan: if pack['aid'] == 'set_chart_data': # 发送的是绘图数据 diff_data = {} # 存储 pack 中的 diff 数据的对象 for series_id, series in pack['datas'].items(): diff_data[series_id] = series if diff_data != {}: web_diff = {'draw_chart_datas': {}} web_diff['draw_chart_datas'][pack['symbol']] = {} web_diff['draw_chart_datas'][pack['symbol']][ pack['dur_nano']] = diff_data _simple_merge_diff(self._data, web_diff) for chan in self._conn_diff_chans: self.send_to_conn_chan(chan, [web_diff]) else: if pack["aid"] == "insert_order": self._order_symbols.add(pack["exchange_id"] + "." + pack["instrument_id"]) if pack['aid'] == 'subscribe_quote' or pack[ "aid"] == "set_chart" or pack[ "aid"] == 'insert_order': web_diff = {'subscribed': []} for item in self._api._requests["klines"].keys(): web_diff['subscribed'].append({ "symbol": item[0], "dur_nano": item[1] * 1000000000 }) for item in self._api._requests["ticks"].keys(): web_diff['subscribed'].append({ "symbol": item[0], "dur_nano": 0 }) for symbol in self._api._requests["quotes"]: web_diff['subscribed'].append( {"symbol": symbol}) for symbol in self._order_symbols: web_diff['subscribed'].append( {"symbol": symbol}) if web_diff['subscribed'] != self._data[ 'subscribed']: self._data['subscribed'] = web_diff[ 'subscribed'] for chan in self._conn_diff_chans: self.send_to_conn_chan(chan, [web_diff]) # 发送的转发给上游 await web_send_chan.send(pack) finally: _data_task.cancel() _httpserver_task.cancel() await asyncio.gather(_data_task, _httpserver_task, return_exceptions=True)
def send_to_conn_chan(self, chan, diffs): last_diff = chan.recv_latest({}) for d in diffs: _simple_merge_diff(last_diff, d, reduce_diff=False) if last_diff != {}: chan.send_nowait(last_diff)