Exemplo n.º 1
0
    def __init__(self, api, symbol, price="ACTIVE", init_pos=None, offset_priority="今昨,开", trade_chan=None):
        """
        创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓)

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            init_pos (int): [可选]初始持仓,默认整个账户的该合净持仓

            offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成

                                   对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓

                                   对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓
                                       * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足
                                       * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种
                                       * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        super(TargetPosTask, self).__init__()
        self.api = api
        self.symbol = symbol
        self.exchange = symbol.split(".")[0]
        self.price = price
        self.pos = self.api.get_position(self.symbol)
        self.current_pos = init_pos
        self.offset_priority = offset_priority
        self.pos_chan = TqChan(self.api, last_only=True)
        self.trade_chan = trade_chan if trade_chan is not None else TqChan(self.api)
        self.task = self.api.create_task(self._target_pos_task())
Exemplo n.º 2
0
    def __init__(self, api, symbol, direction, offset, volume, limit_price=None, order_chan = None, trade_chan = None):
        """
        创建下单task实例

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码,  例如 "SHFE.cu1801"

            direction (str): "BUY" 或 "SELL"

            offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY"

            volume (int): 需要下单的手数

            limit_price (float): [可选]下单价格, 默认市价单

            order_chan (TqChan): [可选]委托单通知channel, 当委托单状态发生时会将委托单信息发到该channel上

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        self.api = api
        self.symbol = symbol
        self.direction = direction
        self.offset = offset
        self.volume = volume
        self.limit_price = limit_price
        self.order_chan = order_chan if order_chan is not None else TqChan(self.api)
        self.trade_chan = trade_chan if trade_chan is not None else TqChan(self.api)
        self.task = self.api.create_task(self._run())
Exemplo n.º 3
0
    def __init__(self,
                 api: TqApi,
                 symbol: str,
                 price: str = "ACTIVE",
                 offset_priority: str = "今昨,开",
                 trade_chan: Optional[TqChan] = None) -> None:
        """
        创建目标持仓task实例,负责调整归属于该task的持仓 **(默认为整个账户的该合约净持仓)**.

        **注意:**
            1. TargetPosTask 在 set_target_volume 时并不下单或撤单, 它的下单和撤单动作, 是在之后的每次 wait_update 时执行的. 因此, **需保证 set_target_volume 后还会继续调用wait_update()** 。

            2. 请勿在使用 TargetPosTask 的同时使用 insert_order() 函数, 否则将导致 TargetPosTask 报错或错误下单。

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单.

                * 在持仓调整过程中,若下单方向为买: 对价为卖一价, 挂价为买一价
                * 在持仓调整过程中,若下单方向为卖: 对价为买一价, 挂价为卖一价

            offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成

                                   对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓
                                   对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓

                                   * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足
                                   * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种
                                   * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        super(TargetPosTask, self).__init__()
        self._api = api
        if symbol not in api._data.get("quotes", {}):
            raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol))
        self._symbol = symbol
        self._exchange = symbol.split(".")[0]
        if price not in ("ACTIVE", "PASSIVE"):
            raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price))
        self._price = price
        if len(
                offset_priority.replace(",", "").replace("今", "", 1).replace(
                    "昨", "", 1).replace("开", "", 1)) > 0:
            raise Exception(
                "开平仓顺序(offset_priority) %s 错误, 请检查 offset_priority 参数是否填写正确" %
                (offset_priority))
        self._offset_priority = offset_priority
        self._pos = self._api.get_position(self._symbol)
        self._pos_chan = TqChan(self._api, last_only=True)
        self._trade_chan = trade_chan if trade_chan is not None else TqChan(
            self._api)
        self._task = self._api.create_task(self._target_pos_task())
Exemplo n.º 4
0
    def __init__(self,
                 api,
                 symbol,
                 direction,
                 offset,
                 volume,
                 limit_price=None,
                 order_chan=None,
                 trade_chan=None):
        """
        创建下单task实例

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码,  例如 "SHFE.cu1801"

            direction (str): "BUY" 或 "SELL"

            offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY"

            volume (int): 需要下单的手数

            limit_price (float): [可选]下单价格, 默认市价单

            order_chan (TqChan): [可选]委托单通知channel, 当委托单状态发生时会将委托单信息发到该channel上

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        self._api = api
        if symbol not in api._data.get("quotes", {}):
            raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol))
        self._symbol = symbol
        if direction not in ("BUY", "SELL"):
            raise Exception("下单方向(direction) %s 错误, 请检查 direction 参数是否填写正确" %
                            (direction))
        self._direction = direction
        if offset not in ("OPEN", "CLOSE", "CLOSETODAY"):
            raise Exception("开平标志(offset) %s 错误, 请检查 offset 是否填写正确" % (offset))
        self._offset = offset
        self._volume = int(volume)
        self._limit_price = float(
            limit_price) if limit_price is not None else None
        self._order_chan = order_chan if order_chan is not None else TqChan(
            self._api)
        self._trade_chan = trade_chan if trade_chan is not None else TqChan(
            self._api)
        self._task = self._api.create_task(self._run())
Exemplo n.º 5
0
 async def _run(self):
     """负责追价下单的task"""
     async with self.api.register_update_notify() as update_chan:
         # 确保获得初始行情
         while self.quote.datetime == "":
             await update_chan.recv()
         while self.volume != 0:
             limit_price = self._get_price()
             insert_order_task = InsertOrderTask(self.api,
                                                 self.symbol,
                                                 self.direction,
                                                 self.offset,
                                                 self.volume,
                                                 limit_price=limit_price,
                                                 trade_chan=self.trade_chan)
             order = await insert_order_task.order_chan.recv()
             check_chan = TqChan(self.api, last_only=True)
             check_task = self.api.create_task(
                 self._check_price(check_chan, limit_price, order))
             try:
                 await insert_order_task.task
                 order = insert_order_task.order_chan.recv_latest(order)
                 self.volume = order.volume_left
                 if self.volume != 0 and not check_task.done():
                     raise Exception(
                         "遇到错单: %s %s %s %d手 %f %s" %
                         (self.symbol, self.direction, self.offset,
                          self.volume, limit_price, order.last_msg))
             finally:
                 await check_chan.close()
                 await check_task
Exemplo n.º 6
0
    def __init__(self, api, symbol, direction, offset, volume, price = "ACTIVE", trade_chan = None):
        """
        创建追价下单task实例

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码,  例如 "SHFE.cu1801"

            direction (str): "BUY" 或 "SELL"

            offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY"

            volume (int): 需要下单的手数

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        self.api = api
        self.symbol = symbol
        self.direction = direction
        self.offset = offset
        self.volume = volume
        self.price = price
        self.trade_chan = trade_chan if trade_chan is not None else TqChan(self.api)
        self.quote = self.api.get_quote(self.symbol)
        self.task = self.api.create_task(self._run())
Exemplo n.º 7
0
    def __init__(self,
                 api,
                 symbol,
                 price="ACTIVE",
                 offset_priority="今昨,开",
                 trade_chan=None):
        """
        创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓)

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成
                                   对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓
                                   对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓

                                   * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足
                                   * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种
                                   * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        super(TargetPosTask, self).__init__()
        self.api = api
        if symbol not in api.data.get("quotes", {}):
            raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol))
        self.symbol = symbol
        self.exchange = symbol.split(".")[0]
        if price not in ("ACTIVE", "PASSIVE"):
            raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price))
        self.price = price
        if len(
                offset_priority.replace(",", "").replace("今", "", 1).replace(
                    "昨", "", 1).replace("开", "", 1)) > 0:
            raise Exception(
                "开平仓顺序(offset_priority) %s 错误, 请检查 offset_priority 参数是否填写正确" %
                (offset_priority))
        self.offset_priority = offset_priority
        self.pos = self.api.get_position(self.symbol)
        self.pos_chan = TqChan(self.api, last_only=True)
        self.trade_chan = trade_chan if trade_chan is not None else TqChan(
            self.api)
        self.task = self.api.create_task(self._target_pos_task())
Exemplo n.º 8
0
    def __init__(self, api, symbol, price="ACTIVE", init_pos=0, trade_chan=None):
        """
        创建目标持仓task实例

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            init_pos (int): [可选]初始持仓,默认为0

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        self.api = api
        self.symbol = symbol
        self.price = price
        self.init_pos = init_pos
        self.pos_chan = TqChan(last_only=True)
        self.trade_chan = trade_chan if trade_chan is not None else TqChan()
        self.task = self.api.create_task(self._target_pos_task())
Exemplo n.º 9
0
 async def _send_snapshot(self):
     async with TqChan(self.api, last_only=True) as update_chan:
         self.data["_listener"].add(update_chan)
         while self.data.get("mdhis_more_data", True):
             await update_chan.recv()
     # 发送合约信息截面
     quotes = {}
     for ins, quote in self.data["quotes"].items():
         if not ins.startswith("_"):
             quotes[ins] = {
                 "datetime": "",
                 "ask_price1": float("nan"),
                 "ask_volume1": 0,
                 "bid_price1": float("nan"),
                 "bid_volume1": 0,
                 "last_price": float("nan"),
                 "highest": float("nan"),
                 "lowest": float("nan"),
                 "open": None,
                 "close": None,
                 "average": float("nan"),
                 "volume": 0,
                 "amount": float("nan"),
                 "open_interest": 0,
                 "settlement": None,
                 "lower_limit": None,
                 "upper_limit": None,
                 "pre_open_interest": None,
                 "pre_settlement": None,
                 "pre_close": None,
                 "price_tick": quote["price_tick"],
                 "price_decs": quote["price_decs"],
                 "volume_multiple": quote["volume_multiple"],
                 "max_limit_order_volume": quote["max_limit_order_volume"],
                 "max_market_order_volume":
                 quote["max_market_order_volume"],
                 "min_limit_order_volume": quote["min_limit_order_volume"],
                 "min_market_order_volume":
                 quote["min_market_order_volume"],
                 "underlying_symbol": quote["underlying_symbol"],
                 "strike_price": quote["strike_price"],
                 "change": None,
                 "change_percent": None,
                 "expired": None,
             }
     self.diffs.append({
         "quotes": quotes,
         "ins_list": "",
         "mdhis_more_data": False,
     })
Exemplo n.º 10
0
 async def _send_snapshot(self):
     """发送初始合约信息"""
     async with TqChan(self.api,
                       last_only=True) as update_chan:  # 等待与行情服务器连接成功
         self.data["_listener"].add(update_chan)
         while self.data.get("mdhis_more_data", True):
             await update_chan.recv()
     # 发送合约信息截面
     quotes = {}
     for ins, quote in self.data["quotes"].items():
         if not ins.startswith("_"):
             quotes[ins] = {
                 "open": None,  # 填写None: 删除api中的这个字段
                 "close": None,
                 "settlement": None,
                 "lower_limit": None,
                 "upper_limit": None,
                 "pre_open_interest": None,
                 "pre_settlement": None,
                 "pre_close": None,
                 "ins_class": quote.get("ins_class", ""),
                 "margin": quote.get(
                     "margin"),  # 用于内部实现模拟交易, 不作为api对外可用数据(即 Quote 类中无此字段)
                 "commission":
                 quote.get("commission"
                           ),  # 用于内部实现模拟交易, 不作为api对外可用数据(即 Quote 类中无此字段)
                 "price_tick": quote["price_tick"],
                 "price_decs": quote["price_decs"],
                 "volume_multiple": quote["volume_multiple"],
                 "max_limit_order_volume": quote["max_limit_order_volume"],
                 "max_market_order_volume":
                 quote["max_market_order_volume"],
                 "min_limit_order_volume": quote["min_limit_order_volume"],
                 "min_market_order_volume":
                 quote["min_market_order_volume"],
                 "underlying_symbol": quote["underlying_symbol"],
                 "strike_price": quote["strike_price"],
                 "expired": None,
                 "trading_time": quote.get("trading_time"),
                 "expire_datetime": quote.get("expire_datetime"),
                 "delivery_month": quote.get("delivery_month"),
                 "delivery_year": quote.get("delivery_year"),
             }
     self.diffs.append({
         "quotes": quotes,
         "ins_list": "",
         "mdhis_more_data": False,
     })
Exemplo n.º 11
0
    def __init__(self,
                 api,
                 symbol,
                 direction,
                 offset,
                 volume,
                 price="ACTIVE",
                 trade_chan=None):
        """
        创建追价下单task实例

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码,  例如 "SHFE.cu1801"

            direction (str): "BUY" 或 "SELL"

            offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY"

            volume (int): 需要下单的手数

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        self._api = api
        if symbol not in api._data.get("quotes", {}):
            raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol))
        self._symbol = symbol
        if direction not in ("BUY", "SELL"):
            raise Exception("下单方向(direction) %s 错误, 请检查 direction 参数是否填写正确" %
                            (direction))
        self._direction = direction
        if offset not in ("OPEN", "CLOSE", "CLOSETODAY"):
            raise Exception("开平标志(offset) %s 错误, 请检查 offset 是否填写正确" % (offset))
        self._offset = offset
        self._volume = int(volume)
        if self._volume <= 0:
            raise Exception("下单手数(volume) %s 错误, 请检查 volume 是否填写正确" % (volume))
        if price not in ("ACTIVE", "PASSIVE"):
            raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price))
        self._price = price
        self._trade_chan = trade_chan if trade_chan is not None else TqChan(
            self._api)
        self._quote = self._api.get_quote(self._symbol)
        self._task = self._api.create_task(self._run())
Exemplo n.º 12
0
 async def _run(self):
     """负责追价下单的task"""
     async with self.api.register_update_notify() as update_chan:
         # 确保获得初始行情
         while self.quote["datetime"] == "":
             await update_chan.recv()
         while self.volume != 0:
             limit_price = self._get_price()
             insert_order_task = InsertOrderTask(self.api, self.symbol, self.direction, self.offset, self.volume, limit_price = limit_price, trade_chan = self.trade_chan)
             order = await insert_order_task.order_chan.recv()
             check_chan = TqChan(last_only=True)
             check_task = self.api.create_task(self._check_price(check_chan, limit_price, order))
             await insert_order_task.task
             await check_chan.close()
             await check_task
             order = insert_order_task.order_chan.recv_latest(order)
             self.volume = order["volume_left"]
Exemplo n.º 13
0
def link_tq(api):
    """
    处理py进程到天勤的连接

    根据天勤提供的命令行参数, 决定 TqApi 工作方式

    * 直接调整api的参数

    TqApi运行过程中的一批信息主动发送到天勤显示

    * 进程启动和停止
    * set_chart_data 指令全部发往天勤绘图
    * 所有 log / print 信息传递一份
    * exception 发送一份
    * 所有报单/成交记录抄送一份

    :return: (account, backtest, md_url)
    """
    from tqsdk.api import TqChan, TqAccount
    from tqsdk.sim import TqSim
    # 解析命令行参数
    parser = argparse.ArgumentParser()
    # 天勤连接基本参数
    parser.add_argument('--_action', type=str, required=False)
    parser.add_argument('--_tq_pid', type=int, required=False)
    parser.add_argument('--_tq_url', type=str, required=False)
    # action==run时需要这几个
    parser.add_argument('--_broker_id', type=str, required=False)
    parser.add_argument('--_account_id', type=str, required=False)
    parser.add_argument('--_password', type=str, required=False)
    # action==backtest时需要这几个
    parser.add_argument('--_start_dt', type=str, required=False)
    parser.add_argument('--_end_dt', type=str, required=False)
    # action==mdreplay时需要这几个
    parser.add_argument('--_ins_url', type=str, required=False)
    parser.add_argument('--_md_url', type=str, required=False)
    args, unknown = parser.parse_known_args()

    # 非天勤启动时直接返回
    if args._action is None:
        return None, None
    if args._tq_pid is None:
        raise Exception("_tq_pid 参数缺失")
    if args._tq_url is None:
        raise Exception("_tq_url 参数缺失")
    if args._action == "run" and (not args._broker_id or not args._account_id
                                  or not args._password):
        raise Exception("run 必要参数缺失")
    if args._action == "backtest" and (not args._start_dt or not args._end_dt):
        raise Exception("backtest 必要参数缺失")
    if args._action == "mdreplay" and (not args._ins_url or not args._md_url):
        raise Exception("mdreplay 必要参数缺失")

    # 监控天勤进程存活情况
    TqMonitorThread(args._tq_pid).start()

    # 建立到天勤进程的连接
    tq_send_chan, tq_recv_chan = TqChan(api), TqChan(api)  # 连接到天勤的channel
    api.create_task(api._connect(args._tq_url, tq_send_chan,
                                 tq_recv_chan))  # 启动到天勤客户端的连接

    # 根据运行模式分别执行不同的初始化任务
    if args._action == "run":
        instance = SingleInstance(args._account_id)
        api._account = TqAccount(args._broker_id, args._account_id,
                                 args._password)
        api._backtest = None
        dt_func = lambda: int(datetime.datetime.now().timestamp() * 1e9)
        tq_send_chan.send_nowait({
            "aid": "register_instance",
            "instance_id": instance.instance_id,
            "full_path": get_self_full_name(),
            "instance_pid": os.getpid(),
            "instance_type": "RUN",
            "broker_id": args._broker_id,
            "account_id": args._account_id,
            "password": args._password,
        })
    elif args._action == "backtest":
        instance = SingleInstance("%s-%s" % (args._start_dt, args._end_dt))
        if not isinstance(api._account, TqSim):
            api._account = TqSim()
        from tqsdk.backtest import TqBacktest
        start_date = datetime.datetime.strptime(args._start_dt, '%Y%m%d')
        end_date = datetime.datetime.strptime(args._end_dt, '%Y%m%d')
        api._backtest = TqBacktest(start_dt=start_date, end_dt=end_date)
        dt_func = lambda: api._account._get_current_timestamp()
        tq_send_chan.send_nowait({
            "aid": "register_instance",
            "instance_id": instance.instance_id,
            "full_path": get_self_full_name(),
            "instance_pid": os.getpid(),
            "instance_type": "BACKTEST",
            "start_dt": args._start_dt,
            "end_dt": args._end_dt,
        })
    elif args._action == "mdreplay":
        instance = SingleInstance(args._account_id)
        api._account = TqSim(account_id=args._account_id)
        api._backtest = None
        api._md_url = args._md_url
        api._ins_url = args._ins_url
        dt_func = lambda: api._account._get_current_timestamp()
        tq_send_chan.send_nowait({
            "aid": "register_instance",
            "instance_id": instance.instance_id,
            "full_path": get_self_full_name(),
            "instance_pid": os.getpid(),
            "instance_type": "RUN",
            "account_id": "SIM",
        })
    else:
        raise Exception("_action 参数异常")

    # print输出, exception信息转发到天勤
    logger = logging.getLogger("TQ")
    logger.setLevel(logging.INFO)
    logger.addHandler(LogHandlerChan(tq_send_chan,
                                     dt_func=dt_func))  # log输出到天勤接口
    sys.stdout = PrintWriterToLog(logger)  # print信息转向log输出
    sys.excepthook = partial(exception_handler, api,
                             sys.excepthook)  # exception信息转向log输出

    # 向api注入监控任务, 将账户交易信息主动推送到天勤
    api.create_task(account_watcher(api, dt_func, tq_send_chan))
    return tq_send_chan, tq_recv_chan
Exemplo n.º 14
0
class TargetPosTask(object):
    """目标持仓 task, 该 task 可以将指定合约调整到目标头寸"""
    def __init__(self,
                 api,
                 symbol,
                 price="ACTIVE",
                 offset_priority="今昨,开",
                 trade_chan=None):
        """
        创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓)

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            init_pos (int): [可选]初始持仓,默认整个账户的该合净持仓

            offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成
                                   对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓
                                   对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓

                                   * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足
                                   * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种
                                   * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        super(TargetPosTask, self).__init__()
        self.api = api
        self.symbol = symbol
        self.exchange = symbol.split(".")[0]
        self.price = price
        self.pos = self.api.get_position(self.symbol)
        self.offset_priority = offset_priority
        self.pos_chan = TqChan(self.api, last_only=True)
        self.trade_chan = trade_chan if trade_chan is not None else TqChan(
            self.api)
        self.task = self.api.create_task(self._target_pos_task())

    def set_target_volume(self, volume):
        """
        设置目标持仓手数

        Args:
            volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓

        Example::

            # 设置 rb1810 持仓为多头5手
            from tqsdk import TqApi, TqSim, TargetPosTask

            api = TqApi(TqSim())
            target_pos = TargetPosTask(api, "SHFE.rb1810")
            target_pos.set_target_volume(5)
            while True:
                api.wait_update()
        """
        self.pos_chan.send_nowait(volume)

    def _get_order(self, offset, vol):
        """
        根据指定的offset和预期下单手数vol, 返回符合要求的委托单最大报单手数
        :param offset: "昨" / "今" / "开"
        :param vol: int, <0表示SELL, >0表示BUY
        :return: order_offset: "CLOSE"/"CLOSETODAY"/"OPEN"; order_dir: "BUY"/"SELL"; "order_volume": >=0, 报单手数
        """
        if vol > 0:  # 买单(增加净持仓)
            order_dir = "BUY"
            pos_all = self.pos.pos_short
        else:  # 卖单
            order_dir = "SELL"
            pos_all = self.pos.pos_long
        if offset == "昨":
            order_offset = "CLOSE"
            if self.exchange == "SHFE" or self.exchange == "INE":
                if vol > 0:
                    pos_all = self.pos.pos_short_his
                else:
                    pos_all = self.pos.pos_long_his
                frozen_volume = sum([
                    order.volume_left for order in self.pos.orders.values()
                    if not order.is_dead() and order.offset == order_offset
                    and order.direction == order_dir
                ])
            else:
                frozen_volume = sum([
                    order.volume_left for order in self.pos.orders.values()
                    if not order.is_dead() and order.offset != "OPEN"
                    and order.direction == order_dir
                ])
            order_volume = max(0, pos_all - frozen_volume)
        elif offset == "今":
            if self.exchange == "SHFE" or self.exchange == "INE":
                order_offset = "CLOSETODAY"
                if vol > 0:
                    pos_all = self.pos.pos_short_today
                else:
                    pos_all = self.pos.pos_long_today
                frozen_volume = sum([
                    order.volume_left for order in self.pos.orders.values()
                    if not order.is_dead() and order.offset == order_offset
                    and order.direction == order_dir
                ])
            else:
                order_offset = "CLOSE"
                frozen_volume = sum([
                    order.volume_left for order in self.pos.orders.values()
                    if not order.is_dead() and order.offset != "OPEN"
                    and order.direction == order_dir
                ])
            order_volume = max(0, pos_all - frozen_volume)
        elif offset == "开":
            order_offset = "OPEN"
            order_volume = abs(vol)
        else:
            order_offset = ""
            order_volume = 0
        return order_offset, order_dir, order_volume

    async def _target_pos_task(self):
        """负责调整目标持仓的task"""
        async for target_pos in self.pos_chan:
            # 确定调仓增减方向
            delta_volume = target_pos - self.pos.pos
            all_tasks = []
            for each_priority in self.offset_priority + ",":  # 按不同模式的优先级顺序报出不同的offset单,股指(“昨开”)平昨优先从不平今就先报平昨,原油平今优先("今昨开")就报平今
                if each_priority == ",":
                    await gather(*[each.task for each in all_tasks])
                    all_tasks = []
                    continue
                order_offset, order_dir, order_volume = self._get_order(
                    each_priority, delta_volume)
                if order_volume == 0:  # 如果没有则直接到下一种offset
                    continue
                order_task = InsertOrderUntilAllTradedTask(
                    self.api,
                    self.symbol,
                    order_dir,
                    offset=order_offset,
                    volume=order_volume,
                    price=self.price,
                    trade_chan=self.trade_chan)
                all_tasks.append(order_task)
                delta_volume -= order_volume if order_dir == "BUY" else -order_volume
            self.current_pos = target_pos
Exemplo n.º 15
0
class TargetPosTask:
    """目标持仓task, 该task可以将指定合约调整到目标头寸"""
    def __init__(self, api, symbol, price="ACTIVE", init_pos=0, trade_chan=None):
        """
        创建目标持仓task实例

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            init_pos (int): [可选]初始持仓,默认为0

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        self.api = api
        self.symbol = symbol
        self.price = price
        self.init_pos = init_pos
        self.pos_chan = TqChan(last_only=True)
        self.trade_chan = trade_chan if trade_chan is not None else TqChan()
        self.task = self.api.create_task(self._target_pos_task())

    def set_target_volume(self, volume):
        """
        设置目标持仓手数

        Args:
            volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓

        Example::

            # 设置 rb1810 持仓为多头5手
            from tqsdk.api import TqApi
            from tqsdk.lib import TargetPosTask

            api = TqApi("SIM")
            target_pos = TargetPosTask(api, "SHFE.rb1810")
            while True:
                api.wait_update()
                target_pos.set_target_volume(5)
        """
        self.pos_chan.put_nowait(volume)

    async def _target_pos_task(self):
        """负责调整目标持仓的task"""
        current_pos = self.init_pos
        async for target_pos in self.pos_chan:
            if (current_pos < 0 and target_pos > current_pos) or (current_pos > 0 and target_pos < current_pos):
                # 平仓
                vol = min(abs(target_pos-current_pos), abs(current_pos))
                insert_order_until_all_traded_task = InsertOrderUntilAllTradedTask(self.api, self.symbol, "BUY" if current_pos < 0 else "SELL", "CLOSE", vol, price = self.price, trade_chan=self.trade_chan)
                await insert_order_until_all_traded_task.task
                current_pos += vol if current_pos < 0 else -vol
            if target_pos != current_pos:
                # 开仓
                vol = target_pos-current_pos
                insert_order_until_all_traded_task = InsertOrderUntilAllTradedTask(self.api, self.symbol, "BUY" if vol > 0 else "SELL", "OPEN", abs(vol), price = self.price, trade_chan=self.trade_chan)
                await insert_order_until_all_traded_task.task
                current_pos += vol
Exemplo n.º 16
0
 async def _gen_serial(self, ins, dur):
     """k线/tick 序列的 async generator, yield 出来的行情数据带有时间戳, 因此 _send_diff 可以据此归并"""
     # 先定位左端点, focus_datetime 是 lower_bound ,这里需要的是 upper_bound
     # 因此将 view_width 和 focus_position 设置成一样,这样 focus_datetime 所对应的 k线刚好位于屏幕外
     chart_info = {
         "aid":
         "set_chart",
         "chart_id":
         TqApi._generate_chart_id("backtest", ins, dur // 1000000000),
         "ins_list":
         ins,
         "duration":
         dur,
         "view_width":
         8964,
         "focus_datetime":
         int(self.current_dt),
         "focus_position":
         8964,
     }
     chart = TqApi._get_obj(self.data, ["charts", chart_info["chart_id"]])
     current_id = None  # 当前数据指针
     serial = TqApi._get_obj(
         self.data,
         ["klines", ins, str(dur)] if dur != 0 else ["ticks", ins])
     async with TqChan(self.api, last_only=True) as update_chan:
         serial["_listener"].add(update_chan)
         chart["_listener"].add(update_chan)
         await self.md_send_chan.send(chart_info.copy())
         try:
             async for _ in update_chan:
                 if not (chart_info.items() <= TqApi._get_obj(
                         chart, ["state"]).items()):
                     # 当前请求还没收齐回应, 不应继续处理
                     continue
                 left_id = chart.get("left_id", -1)
                 right_id = chart.get("right_id", -1)
                 last_id = serial.get("last_id", -1)
                 if (left_id == -1 and right_id == -1) or last_id == -1:
                     # 定位信息还没收到, 或数据序列还没收到
                     continue
                 if self.data.get("mdhis_more_data", True):
                     self.data["_listener"].add(update_chan)
                     continue
                 else:
                     self.data["_listener"].discard(update_chan)
                 if current_id is None:
                     current_id = max(left_id, 0)
                 while True:
                     if current_id > last_id:
                         # 当前 id 已超过 last_id
                         return
                     if current_id - chart_info.get("left_kline_id",
                                                    left_id) > 5000:
                         # 当前 id 已超出订阅范围, 需重新订阅后续数据
                         chart_info["left_kline_id"] = current_id
                         chart_info.pop("focus_datetime", None)
                         chart_info.pop("focus_position", None)
                         await self.md_send_chan.send(chart_info.copy())
                     if current_id > right_id:
                         break
                     item = serial["data"].get(str(current_id), {}).copy()
                     del item["_path"]
                     del item["_listener"]
                     if dur == 0:
                         diff = {
                             "ticks": {
                                 ins: {
                                     "last_id": current_id,
                                     "data": {
                                         str(current_id): item,
                                         str(current_id - 8964): None,
                                     }
                                 }
                             }
                         }
                         if item["datetime"] > self.end_dt:  # 超过结束时间
                             return
                         yield item[
                             "datetime"], diff, self._get_quotes_from_tick(
                                 item)
                     else:
                         diff = {
                             "klines": {
                                 ins: {
                                     str(dur): {
                                         "last_id": current_id,
                                         "data": {
                                             str(current_id): {
                                                 "datetime":
                                                 item["datetime"],
                                                 "open": item["open"],
                                                 "high": item["open"],
                                                 "low": item["open"],
                                                 "close": item["open"],
                                                 "volume": 0,
                                                 "open_oi": item["open_oi"],
                                                 "close_oi":
                                                 item["open_oi"],
                                             },
                                             str(current_id - 8964): None,
                                         }
                                     }
                                 }
                             }
                         }
                         timestamp = item[
                             "datetime"] if dur < 86400000000000 else TqApi._get_trading_day_start_time(
                                 item["datetime"])
                         if timestamp > self.end_dt:  # 超过结束时间
                             return
                         yield timestamp, diff, None
                         diff = {
                             "klines": {
                                 ins: {
                                     str(dur): {
                                         "data": {
                                             str(current_id): item,
                                         }
                                     }
                                 }
                             }
                         }
                         timestamp = item[
                             "datetime"] + dur - 1000 if dur < 86400000000000 else TqApi._get_trading_day_end_time(
                                 item["datetime"])
                         if timestamp > self.end_dt:  # 超过结束时间
                             return
                         yield timestamp, diff, self._get_quotes_from_kline(
                             self.data["quotes"][ins], timestamp, item)
                     current_id += 1
         finally:
             # 释放chart资源
             chart_info["ins_list"] = ""
             await self.md_send_chan.send(chart_info.copy())
Exemplo n.º 17
0
class TargetPosTask(object, metaclass=TargetPosTaskSingleton):
    """目标持仓 task, 该 task 可以将指定合约调整到目标头寸"""
    def __init__(self,
                 api: TqApi,
                 symbol: str,
                 price: str = "ACTIVE",
                 offset_priority: str = "今昨,开",
                 trade_chan: Optional[TqChan] = None) -> None:
        """
        创建目标持仓task实例,负责调整归属于该task的持仓 **(默认为整个账户的该合约净持仓)**.

        **注意:** TargetPosTask 在 set_target_volume 时并不下单或撤单, 它的下单和撤单动作, 是在之后的每次 wait_update 时执行的. 因此, **需保证 set_target_volume 后还会继续调用wait_update()**

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单.

                * 在持仓调整过程中,若下单方向为买: 对价为卖一价, 挂价为买一价
                * 在持仓调整过程中,若下单方向为卖: 对价为买一价, 挂价为卖一价

            offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成

                                   对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓
                                   对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓

                                   * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足
                                   * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种
                                   * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        super(TargetPosTask, self).__init__()
        self._api = api
        if symbol not in api._data.get("quotes", {}):
            raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol))
        self._symbol = symbol
        self._exchange = symbol.split(".")[0]
        if price not in ("ACTIVE", "PASSIVE"):
            raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price))
        self._price = price
        if len(
                offset_priority.replace(",", "").replace("今", "", 1).replace(
                    "昨", "", 1).replace("开", "", 1)) > 0:
            raise Exception(
                "开平仓顺序(offset_priority) %s 错误, 请检查 offset_priority 参数是否填写正确" %
                (offset_priority))
        self._offset_priority = offset_priority
        self._pos = self._api.get_position(self._symbol)
        self._pos_chan = TqChan(self._api, last_only=True)
        self._trade_chan = trade_chan if trade_chan is not None else TqChan(
            self._api)
        self._task = self._api.create_task(self._target_pos_task())

    def set_target_volume(self, volume: int) -> None:
        """
        设置目标持仓手数

        Args:
            volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓

        Example::

            # 设置 rb1810 持仓为多头5手
            from tqsdk import TqApi, TargetPosTask

            api = TqApi()
            target_pos = TargetPosTask(api, "SHFE.rb1810")
            target_pos.set_target_volume(5)
            while True:
                # 需在 set_target_volume 后调用wait_update()以发出指令
                api.wait_update()
        """
        self._pos_chan.send_nowait(int(volume))

    def _get_order(self, offset, vol, pending_frozen):
        """
        根据指定的offset和预期下单手数vol, 返回符合要求的委托单最大报单手数
        :param offset: "昨" / "今" / "开"
        :param vol: int, <0表示SELL, >0表示BUY
        :return: order_offset: "CLOSE"/"CLOSETODAY"/"OPEN"; order_dir: "BUY"/"SELL"; "order_volume": >=0, 报单手数
        """
        if vol > 0:  # 买单(增加净持仓)
            order_dir = "BUY"
            pos_all = self._pos.pos_short
        else:  # 卖单
            order_dir = "SELL"
            pos_all = self._pos.pos_long
        if offset == "昨":
            order_offset = "CLOSE"
            if self._exchange == "SHFE" or self._exchange == "INE":
                if vol > 0:
                    pos_all = self._pos.pos_short_his
                else:
                    pos_all = self._pos.pos_long_his
                frozen_volume = sum([
                    order.volume_left for order in self._pos.orders.values()
                    if not order.is_dead and order.offset == order_offset
                    and order.direction == order_dir
                ])
            else:
                frozen_volume = pending_frozen + sum([
                    order.volume_left
                    for order in self._pos.orders.values() if not order.is_dead
                    and order.offset != "OPEN" and order.direction == order_dir
                ])
                # 判断是否有未冻结的今仓手数: 若有则不平昨仓
                if (self._pos.pos_short_today if vol > 0 else
                        self._pos.pos_long_today) - frozen_volume > 0:
                    pos_all = frozen_volume
            order_volume = min(abs(vol), max(0, pos_all - frozen_volume))
        elif offset == "今":
            if self._exchange == "SHFE" or self._exchange == "INE":
                order_offset = "CLOSETODAY"
                if vol > 0:
                    pos_all = self._pos.pos_short_today
                else:
                    pos_all = self._pos.pos_long_today
                frozen_volume = sum([
                    order.volume_left for order in self._pos.orders.values()
                    if not order.is_dead and order.offset == order_offset
                    and order.direction == order_dir
                ])
            else:
                order_offset = "CLOSE"
                frozen_volume = pending_frozen + sum([
                    order.volume_left
                    for order in self._pos.orders.values() if not order.is_dead
                    and order.offset != "OPEN" and order.direction == order_dir
                ])
                pos_all = self._pos.pos_short_today if vol > 0 else self._pos.pos_long_today
            order_volume = min(abs(vol), max(0, pos_all - frozen_volume))
        elif offset == "开":
            order_offset = "OPEN"
            order_volume = abs(vol)
        else:
            order_offset = ""
            order_volume = 0
        return order_offset, order_dir, order_volume

    async def _target_pos_task(self):
        """负责调整目标持仓的task"""
        async for target_pos in self._pos_chan:
            # 确定调仓增减方向
            delta_volume = target_pos - self._pos.pos
            pending_forzen = 0
            all_tasks = []
            for each_priority in self._offset_priority + ",":  # 按不同模式的优先级顺序报出不同的offset单,股指(“昨开”)平昨优先从不平今就先报平昨,原油平今优先("今昨开")就报平今
                if each_priority == ",":
                    await gather(*[each._task for each in all_tasks])
                    pending_forzen = 0
                    all_tasks = []
                    continue
                order_offset, order_dir, order_volume = self._get_order(
                    each_priority, delta_volume, pending_forzen)
                if order_volume == 0:  # 如果没有则直接到下一种offset
                    continue
                elif order_offset != "OPEN":
                    pending_forzen += order_volume
                order_task = InsertOrderUntilAllTradedTask(
                    self._api,
                    self._symbol,
                    order_dir,
                    offset=order_offset,
                    volume=order_volume,
                    price=self._price,
                    trade_chan=self._trade_chan)
                all_tasks.append(order_task)
                delta_volume -= order_volume if order_dir == "BUY" else -order_volume
Exemplo n.º 18
0
class TargetPosTask(object):
    """目标持仓 task, 该 task 可以将指定合约调整到目标头寸"""
    def __init__(self,
                 api,
                 symbol,
                 price="ACTIVE",
                 init_pos=None,
                 offset_priority="今昨,开",
                 trade_chan=None):
        """
        创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓)

        Args:
            api (TqApi): TqApi实例,该task依托于指定api下单/撤单

            symbol (str): 负责调整的合约代码

            price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单

            init_pos (int): [可选]初始持仓,默认整个账户的该合净持仓

            offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成

                                   对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓

                                   对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓
                                       * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足
                                       * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种
                                       * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种

            trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上
        """
        super(TargetPosTask, self).__init__()
        self.api = api
        self.symbol = symbol
        self.exchange = symbol.split(".")[0]
        self.price = price
        self.pos = self.api.get_position(self.symbol)
        self.current_pos = init_pos
        self.offset_priority = offset_priority
        self.pos_chan = TqChan(self.api, last_only=True)
        self.trade_chan = trade_chan if trade_chan is not None else TqChan(
            self.api)
        self.task = self.api.create_task(self._target_pos_task())

    def set_target_volume(self, volume):
        """
        设置目标持仓手数

        Args:
            volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓

        Example::

            # 设置 rb1810 持仓为多头5手
            from tqsdk import TqApi, TqSim, TargetPosTask

            api = TqApi(TqSim())
            target_pos = TargetPosTask(api, "SHFE.rb1810")
            while True:
                api.wait_update()
                target_pos.set_target_volume(5)
        """
        self.pos_chan.send_nowait(volume)

    def _init_position(self):
        """初始化当前持仓"""
        if self.current_pos is None:
            self.current_pos = self.pos["volume_long_today"] + self.pos[
                "volume_long_his"] - self.pos["volume_short_today"] - self.pos[
                    "volume_short_his"]

    def _get_order(self, offset, vol, pos):
        """获得可平手数"""
        if vol > 0:  # 买单(增加净持仓)
            order_dir = "BUY"
            ydAvailable = pos["volume_short_his"] - (
                pos["volume_short_frozen"] - pos["volume_short_frozen_today"]
            )  # 昨空可用
            tdAvailable = pos["volume_short_today"] - pos[
                "volume_short_frozen_today"]  # 今空可用
        else:  # 卖单
            order_dir = "SELL"
            ydAvailable = pos["volume_long_his"] - (
                pos["volume_long_frozen"] - pos["volume_long_frozen_today"]
            )  # 昨多可用
            tdAvailable = pos["volume_long_today"] - pos[
                "volume_long_frozen_today"]  # 今多可用
        if offset == "昨":
            order_offset = "CLOSE"
            order_volume = min(
                abs(vol), ydAvailable if self.exchange == "SHFE"
                or self.exchange == "INE" or tdAvailable == 0 else 0)
            if vol > 0:
                pos["volume_short_frozen"] += order_volume
                pos["volume_short_frozen_his"] += order_volume
            else:
                pos["volume_long_frozen"] += order_volume
                pos["volume_long_frozen_his"] += order_volume
        elif offset == "今":
            order_offset = "CLOSETODAY" if self.exchange == "SHFE" or self.exchange == "INE" else "CLOSE"
            order_volume = min(
                abs(vol), tdAvailable if self.exchange == "SHFE"
                or self.exchange == "INE" else tdAvailable + ydAvailable)
            if vol > 0:
                pos["volume_short_frozen"] += order_volume
                pos["volume_short_frozen_today"] += order_volume
                pos["volume_short_frozen_his"] += max(
                    0, pos["volume_short_frozen_today"] -
                    pos["volume_short_today"])
                pos["volume_short_frozen_today"] = min(
                    pos["volume_short_frozen_today"],
                    pos["volume_short_today"])
            else:
                pos["volume_long_frozen"] += order_volume
                pos["volume_long_frozen_today"] += order_volume
                pos["volume_long_frozen_his"] += max(
                    0,
                    pos["volume_long_frozen_today"] - pos["volume_long_today"])
                pos["volume_long_frozen_today"] = min(
                    pos["volume_long_frozen_today"], pos["volume_long_today"])
        elif offset == "开":
            order_offset = "OPEN"
            order_volume = abs(vol)
        else:
            order_offset = ""
            order_volume = 0
        return order_offset, order_dir, order_volume

    async def _target_pos_task(self):
        """负责调整目标持仓的task"""
        self._init_position()
        async for target_pos in self.pos_chan:
            # 确定调仓增减方向
            delta_volume = target_pos - self.current_pos
            all_tasks = []
            pos = self.pos.copy()
            for each_priority in self.offset_priority + ",":  # 按不同模式的优先级顺序报出不同的offset单,股指(“昨开”)平昨优先从不平今就先报平昨,原油平今优先("今昨开")就报平今
                if each_priority == ",":
                    await gather(*[each.task for each in all_tasks])
                    all_tasks = []
                    pos = self.pos.copy()
                    continue
                order_offset, order_dir, order_volume = self._get_order(
                    each_priority, delta_volume, pos)
                if order_volume == 0:  # 如果没有则直接到下一种offset
                    continue
                order_task = InsertOrderUntilAllTradedTask(
                    self.api,
                    self.symbol,
                    order_dir,
                    offset=order_offset,
                    volume=order_volume,
                    price=self.price,
                    trade_chan=self.trade_chan)
                all_tasks.append(order_task)
                delta_volume -= order_volume if order_dir == "BUY" else -order_volume
            self.current_pos = target_pos