Exemple #1
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)
Exemple #2
0
 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)
Exemple #3
0
 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
Exemple #4
0
    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
Exemple #5
0
 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()
Exemple #6
0
    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)
Exemple #7
0
 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)
Exemple #9
0
    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)
Exemple #10
0
 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)