Esempio n. 1
0
    def _match_order(self, order, symbol, quote, underlying_quote=None):
        ask_price = quote["ask_price1"]
        bid_price = quote["bid_price1"]
        if quote["ins_class"] == "FUTURE_INDEX":
            # 在指数交易时,使用 tick 进行回测时,backtest 发的 quote 没有买一卖一价;或者在实时行情中,指数的 quote 也没有买一卖一价
            if ask_price != ask_price:
                ask_price = quote["last_price"] + quote["price_tick"]
            if bid_price != bid_price:
                bid_price = quote["last_price"] - quote["price_tick"]

        if "limit_price" not in order:
            price = ask_price if order["direction"] == "BUY" else bid_price
            if price != price:
                return "市价指令剩余撤销"
        elif order["direction"] == "BUY" and order["limit_price"] >= ask_price:
            price = order["limit_price"]
        elif order["direction"] == "SELL" and order["limit_price"] <= bid_price:
            price = order["limit_price"]
        elif order["time_condition"] == "IOC":  # IOC 立即成交,限价下单且不能成交的价格,直接撤单
            return "已撤单报单已提交"
        else:
            return ""
        trade = {
            "user_id": order["user_id"],
            "order_id": order["order_id"],
            "trade_id": order["order_id"] + "|" + str(order["volume_left"]),
            "exchange_trade_id": order["order_id"] + "|" + str(order["volume_left"]),
            "exchange_id": order["exchange_id"],
            "instrument_id": order["instrument_id"],
            "direction": order["direction"],
            "offset": order["offset"],
            "price": price,
            "volume": order["volume_left"],
            # todo: 可能导致测试结果不确定
            "trade_date_time": _get_trade_timestamp(self._current_datetime, self._local_time_record),
            # 期权quote没有commission字段, 设为固定10元一张
            "commission": (quote["commission"] if quote["ins_class"] not in ["OPTION", "FUTURE_OPTION"] else 10) *
                          order["volume_left"],
        }
        trade_log = self._ensure_trade_log()
        trade_log["trades"].append(trade)
        self._send_trade(trade)
        if order["exchange_id"] == "SHFE" or order["exchange_id"] == "INE":
            priority = "H" if order["offset"] == "CLOSE" else "T"
        else:
            priority = "TH"
        if order["offset"].startswith("CLOSE"):
            volume_long = 0 if order["direction"] == "BUY" else -order["volume_left"]
            volume_short = 0 if order["direction"] == "SELL" else -order["volume_left"]
            self._adjust_position(symbol, volume_long_frozen=volume_long, volume_short_frozen=volume_short,
                                  priority=priority)
        else:
            volume_long = 0 if order["direction"] == "SELL" else order["volume_left"]
            volume_short = 0 if order["direction"] == "BUY" else order["volume_left"]
        self._adjust_position(symbol, volume_long=volume_long, volume_short=volume_short, price=price,
                              priority=priority)
        premium = -order["frozen_premium"] if order["direction"] == "BUY" else order["frozen_premium"]
        self._adjust_account(commission=trade["commission"], premium=premium)
        order["volume_left"] = 0
        return "全部成交"
Esempio n. 2
0
 async def _insert_order(self, volume, end_time, strict_end_time, exit_immediately):
     volume_left = volume
     try:
         trade_chan = TqChan(self._api)
         self._order_task = InsertOrderUntilAllTradedTask(self._api, self._symbol, self._direction, self._offset,
                                                          volume=volume, price="PASSIVE", trade_chan=trade_chan,
                                                          trade_objs_chan=self._trade_objs_chan,
                                                          account=self._account)
         async with self._api.register_update_notify() as update_chan:
             async for _ in update_chan:
                 if _get_trade_timestamp(self._quote.datetime, float('nan')) > strict_end_time:
                     break
                 else:
                     while not trade_chan.empty():
                         v = await trade_chan.recv()
                         volume_left = volume_left - (v if self._direction == "BUY" else -v)
                     if exit_immediately and volume_left == 0:
                         break
     finally:
         self._order_task._task.cancel()
         await asyncio.gather(self._order_task._task, return_exceptions=True)
         while not trade_chan.empty():
             v = await trade_chan.recv()
             volume_left = volume_left - (v if self._direction == "BUY" else -v)
         await trade_chan.close()
         if volume_left > 0:
             await self._insert_order_active(volume_left)
Esempio n. 3
0
 def _get_deadline_timestamp(self, interval_list):
     # interval - min(5, interval / 3)  # 定义一个时间片段中,开始到快要结束的时间间隔
     # 使用 rangeSet 计算出时间段;都使用本地时间或者都使用 **行情时间**
     # 当前交易日完整的交易时间段
     trading_timestamp = _get_trading_timestamp(self._quote,
                                                self._quote.datetime)
     trading_timestamp_nano_range = trading_timestamp[
         'night'] + trading_timestamp['day']  # 当前交易日完整的交易时间段
     # 当前时间 行情时间
     current_timestamp_nano = _get_trade_timestamp(self._quote.datetime,
                                                   float('nan'))
     if not trading_timestamp_nano_range[0][
             0] <= current_timestamp_nano < trading_timestamp_nano_range[
                 -1][1]:
         raise Exception("当前时间不在指定的交易时间段内")
     # 此时,current_timestamp_nano 一定在此交易日内
     deadline_timestamp_list = []
     strict_deadline_timestamp_list = []
     for interval in interval_list:
         r = _rangeset_head(
             _rangeset_slice(trading_timestamp_nano_range,
                             current_timestamp_nano), int(interval * 1e9))
         strict_interval = interval - min(2, interval / 3)
         strict_r = _rangeset_head(
             _rangeset_slice(trading_timestamp_nano_range,
                             current_timestamp_nano),
             int(strict_interval * 1e9))
         if _rangeset_length(r) < int(interval * 1e9):
             raise Exception("指定时间段超出当前交易日")
         deadline_timestamp_list.append(r[-1][1])
         strict_deadline_timestamp_list.append(strict_r[-1][1])
         current_timestamp_nano = r[-1][1]
     return deadline_timestamp_list, strict_deadline_timestamp_list
Esempio n. 4
0
def _get_dividend_ratio(quote):
    # 获取合约下一个交易日的送股、分红信息
    timestamp = _get_trading_day_from_timestamp(
        _get_trade_timestamp(quote['datetime'], float('nan')) +
        86400000000000)  # 下一交易日
    stock_dividend = _get_dividend_ratio_by_dt(quote['stock_dividend_ratio'],
                                               timestamp=timestamp)
    cash_dividend = _get_dividend_ratio_by_dt(quote['cash_dividend_ratio'],
                                              timestamp=timestamp)
    return stock_dividend, cash_dividend
Esempio n. 5
0
 def _insert_order(self, order, symbol, quote, underlying_quote=None):
     if order["offset"].startswith("CLOSE"):
         volume_long_frozen = 0 if order["direction"] == "BUY" else order["volume_left"]
         volume_short_frozen = 0 if order["direction"] == "SELL" else order["volume_left"]
         if order["exchange_id"] == "SHFE" or order["exchange_id"] == "INE":
             priority = "H" if order["offset"] == "CLOSE" else "T"
         else:
             priority = "TH"
         if not self._adjust_position(symbol, volume_long_frozen=volume_long_frozen,
                                      volume_short_frozen=volume_short_frozen, priority=priority):
             return "平仓手数不足"
     else:
         if ("commission" not in quote or "margin" not in quote) \
                 and quote["ins_class"] not in ["OPTION", "FUTURE_OPTION"]:
             # 除了期权外,主连、指数和组合没有这两个字段
             return "合约不存在"
         if quote["ins_class"] in ["OPTION", "FUTURE_OPTION"]:
             if order["price_type"] == "ANY" and order["exchange_id"] != "CZCE":
                 return f"此交易所({order['exchange_id']}) 不支持期权市价单"
             elif order["direction"] == "SELL":  # 期权的SELL义务仓
                 if quote["option_class"] == "CALL":
                     # 认购期权义务仓开仓保证金=[合约最新价 + Max(12% × 合约标的最新价 - 认购期权虚值, 7% × 合约标的前收盘价)] × 合约单位
                     # 认购期权虚值=Max(行权价 - 合约标的前收盘价,0);
                     order["frozen_margin"] = (quote["last_price"] + max(
                         0.12 * underlying_quote["last_price"] - max(
                             quote["strike_price"] - underlying_quote["last_price"], 0),
                         0.07 * underlying_quote["last_price"])) * quote["volume_multiple"]
                 else:
                     # 认沽期权义务仓开仓保证金=Min[合约最新价+ Max(12% × 合约标的前收盘价 - 认沽期权虚值,7%×行权价),行权价] × 合约单位
                     # 认沽期权虚值=Max(合约标的前收盘价 - 行权价,0)
                     order["frozen_margin"] = min(quote["last_price"] + max(
                         0.12 * underlying_quote["last_price"] - max(
                             underlying_quote["last_price"] - quote["strike_price"], 0),
                         0.07 * quote["strike_price"]), quote["strike_price"]) * quote["volume_multiple"]
             elif order["price_type"] != "ANY":  # 期权的BUY权利仓(市价单立即成交且没有limit_price字段,frozen_premium默认为0)
                 order["frozen_premium"] = order["volume_orign"] * quote["volume_multiple"] * order[
                     "limit_price"]
         else:  # 期货
             # 市价单立即成交或不成, 对api来说普通市价单没有 冻结_xxx 数据存在的状态
             order["frozen_margin"] = quote["margin"] * order["volume_orign"]
         if not self._adjust_account(frozen_margin=order["frozen_margin"],
                                     frozen_premium=order["frozen_premium"]):
             return "开仓资金不足"
     # 可以模拟交易下单
     order["insert_date_time"] = _get_trade_timestamp(self._current_datetime, self._local_time_record)
     self._send_order(order)
     self._logger.info("模拟交易下单 %s: 时间:%s,合约:%s,开平:%s,方向:%s,手数:%s,价格:%s", order["order_id"],
                       datetime.fromtimestamp(order["insert_date_time"] / 1e9).strftime(
                           "%Y-%m-%d %H:%M:%S.%f"), symbol, order["offset"], order["direction"],
                       order["volume_left"], order.get("limit_price", "市价"))
     if not _is_in_trading_time(quote, self._current_datetime, self._local_time_record):
         return "下单失败, 不在可交易时间段内"
Esempio n. 6
0
def _get_deadline_from_interval(quote, interval):
    """将 interval (持续长度 seconds)列转换为 deadline(结束时间 nano_timestamp)"""
    # 当前交易日完整的交易时间段
    trading_timestamp = _get_trading_timestamp(quote, quote.datetime)
    trading_timestamp_nano_range = trading_timestamp['night'] + trading_timestamp['day']  # 当前交易日完整的交易时间段
    # 当前时间 行情时间
    current_timestamp_nano = _get_trade_timestamp(quote.datetime, float('nan'))
    if not trading_timestamp_nano_range[0][0] <= current_timestamp_nano < trading_timestamp_nano_range[-1][1]:
        raise Exception("当前时间不在指定的交易时间段内")
    deadline = []
    for index, value in interval.items():
        r = _rangeset_head(_rangeset_slice(trading_timestamp_nano_range, current_timestamp_nano), int(value * 1e9))
        if _rangeset_length(r) < int(value * 1e9):
            raise Exception("指定时间段超出当前交易日")
        deadline.append(r[-1][1])
        current_timestamp_nano = r[-1][1]
    return deadline
 async def _run(self):
     """负责调整目标持仓的task"""
     quote = await self._api.get_quote(self._symbol)
     self._time_table['deadline'] = _get_deadline_from_interval(
         quote, self._time_table['interval'])
     target_pos_task = None
     try:
         _index = 0  # _index 表示下标
         for index, row in self._time_table.iterrows():
             if row['price'] is None:
                 target_pos_task = None
             else:
                 target_pos_task = TargetPosTask(
                     api=self._api,
                     symbol=self._symbol,
                     price=row['price'],
                     offset_priority=self._offset_priority,
                     min_volume=self._min_volume,
                     max_volume=self._max_volume,
                     trade_chan=self._trade_chan,
                     trade_objs_chan=self._trade_objs_chan,
                     account=self._account)
                 target_pos_task.set_target_volume(row['target_pos'])
             if _index < self._time_table.shape[0] - 1:  # 非最后一项
                 async for _ in self._api.register_update_notify(quote):
                     if _get_trade_timestamp(
                             quote.datetime,
                             float('nan')) > row['deadline']:
                         if target_pos_task:
                             target_pos_task._task.cancel()
                             await asyncio.gather(target_pos_task._task,
                                                  return_exceptions=True)
                         break
             elif target_pos_task:  # 最后一项,如果有 target_pos_task 等待持仓调整完成,否则直接退出
                 position = self._account.get_position(self._symbol)
                 async for _ in self._api.register_update_notify(position):
                     if position.pos == row['target_pos']:
                         break
             _index = _index + 1
     finally:
         if target_pos_task:
             target_pos_task._task.cancel()
             await asyncio.gather(target_pos_task._task,
                                  return_exceptions=True)
         await self._trade_objs_chan.close()
         await self._trade_recv_task
Esempio n. 8
0
 def _get_trade_timestamp(self):
     return _get_trade_timestamp(self._current_datetime,
                                 self._local_time_record)
Esempio n. 9
0
    def _match_order(self, quote, order):
        underlying_quote = self._ensure_quote(quote["underlying_symbol"])
        # 如果未收到行情,不处理
        if quote["datetime"] == "" or (
                quote["ins_class"] in ["OPTION", "FUTURE_OPTION"] and underlying_quote["datetime"] == ""):
            return
        symbol = order["exchange_id"] + "." + order["instrument_id"]

        cancel_order_flag = False  # 是否需要在不能成交的时候撤掉该委托单,适用于连着发下单和撤单指令时
        if order["insert_date_time"] <= 0:  # 此字段已在_insert_order()初始化为0,或在cancel_order置为-1
            # order初始化时计算期权的frozen_margin需要使用行情数据,因此等待收到行情后再调整初始化字段的方案:
            # 在_insert_order()只把order存在quote下的orders字典中,然后在match_order()判断收到行情后从根据insert_order_time来判断此委托单是否已初始化.
            if order["offset"].startswith("CLOSE"):
                volume_long_frozen = 0 if order["direction"] == "BUY" else order["volume_left"]
                volume_short_frozen = 0 if order["direction"] == "SELL" else order["volume_left"]
                if order["exchange_id"] == "SHFE" or order["exchange_id"] == "INE":
                    priority = "H" if order["offset"] == "CLOSE" else "T"
                else:
                    priority = "TH"
                if not self._adjust_position(symbol, volume_long_frozen=volume_long_frozen,
                                             volume_short_frozen=volume_short_frozen, priority=priority):
                    self._del_order(order, "平仓手数不足")
                    return
            else:
                if (quote["commission"] is None or quote["margin"] is None) and quote["ins_class"] not in ["OPTION",
                                                                                                           "FUTURE_OPTION"]:
                    self._del_order(order, "合约不存在")  # 除了期权外,主连、指数和组合没有这两个字段
                    return
                if quote["ins_class"] in ["OPTION", "FUTURE_OPTION"]:
                    if order["price_type"] == "ANY" and order["exchange_id"] != "CZCE":
                        self._del_order(order, "此交易所(" + order["exchange_id"] + ")不支持期权市价单")
                        return
                    elif order["direction"] == "SELL":  # 期权的SELL义务仓
                        if quote["option_class"] == "CALL":
                            # 认购期权义务仓开仓保证金=[合约最新价 + Max(12% × 合约标的最新价 - 认购期权虚值, 7% × 合约标的前收盘价)] × 合约单位
                            # 认购期权虚值=Max(行权价 - 合约标的前收盘价,0);
                            order["frozen_margin"] = (quote["last_price"] + max(
                                0.12 * underlying_quote["last_price"] - max(
                                    quote["strike_price"] - underlying_quote["last_price"], 0),
                                0.07 * underlying_quote["last_price"])) * quote["volume_multiple"]
                        else:
                            # 认沽期权义务仓开仓保证金=Min[合约最新价+ Max(12% × 合约标的前收盘价 - 认沽期权虚值,7%×行权价),行权价] × 合约单位
                            # 认沽期权虚值=Max(合约标的前收盘价 - 行权价,0)
                            order["frozen_margin"] = min(quote["last_price"] + max(
                                0.12 * underlying_quote["last_price"] - max(
                                    underlying_quote["last_price"] - quote["strike_price"], 0),
                                0.07 * quote["strike_price"]), quote["strike_price"]) * quote["volume_multiple"]
                    elif order["price_type"] != "ANY":  # 期权的BUY权利仓(市价单立即成交且没有limit_price字段,frozen_premium默认为0)
                        order["frozen_premium"] = order["volume_orign"] * quote["volume_multiple"] * order[
                            "limit_price"]
                else:  # 期货
                    # 市价单立即成交或不成, 对api来说普通市价单没有 冻结_xxx 数据存在的状态
                    order["frozen_margin"] = quote["margin"] * order["volume_orign"]
                if not self._adjust_account(frozen_margin=order["frozen_margin"],
                                            frozen_premium=order["frozen_premium"]):
                    self._del_order(order, "开仓资金不足")
                    return

            # 需在收到quote行情时, 才将其order的diff下发并将“模拟交易下单”logger发出(即可保证order的insert_date_time为正确的行情时间)
            # 方案为:通过在 match_order() 中判断 “inster_datetime” 来处理:
            # 则能判断收到了行情,又根据 “inster_datetime” 判断了是下单后还未处理(即diff下发和生成logger info)过的order.
            if order["insert_date_time"] == -1:  # 如果等待撤单
                cancel_order_flag = True
            order["insert_date_time"] = _get_trade_timestamp(self._current_datetime, self._local_time_record)
            self._send_order(order)
            self._logger.info("模拟交易下单 %s: 时间:%s,合约:%s,开平:%s,方向:%s,手数:%s,价格:%s", order["order_id"],
                              datetime.datetime.fromtimestamp(order["insert_date_time"] / 1e9).strftime(
                                  "%Y-%m-%d %H:%M:%S.%f"), symbol, order["offset"], order["direction"],
                              order["volume_left"], order.get("limit_price", "市价"))
            if not _is_in_trading_time(quote, self._current_datetime, self._local_time_record):
                self._del_order(order, "下单失败, 不在可交易时间段内")
                return

        ask_price = quote["ask_price1"]
        bid_price = quote["bid_price1"]
        if quote["ins_class"] == "FUTURE_INDEX":
            # 在指数交易时,使用 tick 进行回测时,backtest 发的 quote 没有买一卖一价;或者在实时行情中,指数的 quote 也没有买一卖一价
            if ask_price != ask_price:
                ask_price = quote["last_price"] + quote["price_tick"]
            if bid_price != bid_price:
                bid_price = quote["last_price"] - quote["price_tick"]

        if "limit_price" not in order:
            price = ask_price if order["direction"] == "BUY" else bid_price
            if price != price:
                self._del_order(order, "市价指令剩余撤销")
                return
        elif order["direction"] == "BUY" and order["limit_price"] >= ask_price:
            price = order["limit_price"]
        elif order["direction"] == "SELL" and order["limit_price"] <= bid_price:
            price = order["limit_price"]
        elif cancel_order_flag:
            self._del_order(order, "已撤单")
            return
        else:
            return
        trade = {
            "user_id": order["user_id"],
            "order_id": order["order_id"],
            "trade_id": order["order_id"] + "|" + str(order["volume_left"]),
            "exchange_trade_id": order["order_id"] + "|" + str(order["volume_left"]),
            "exchange_id": order["exchange_id"],
            "instrument_id": order["instrument_id"],
            "direction": order["direction"],
            "offset": order["offset"],
            "price": price,
            "volume": order["volume_left"],
            # todo: 可能导致测试结果不确定
            "trade_date_time": _get_trade_timestamp(self._current_datetime, self._local_time_record),
            # 期权quote没有commission字段, 设为固定10元一张
            "commission": (quote["commission"] if quote["ins_class"] not in ["OPTION", "FUTURE_OPTION"] else 10) *
                          order["volume_left"],
        }
        trade_log = self._ensure_trade_log()
        trade_log["trades"].append(trade)
        self._diffs.append({
            "trade": {
                self._account_id: {
                    "trades": {
                        trade["trade_id"]: trade.copy()
                    }
                }
            }
        })
        if order["exchange_id"] == "SHFE" or order["exchange_id"] == "INE":
            priority = "H" if order["offset"] == "CLOSE" else "T"
        else:
            priority = "TH"
        if order["offset"].startswith("CLOSE"):
            volume_long = 0 if order["direction"] == "BUY" else -order["volume_left"]
            volume_short = 0 if order["direction"] == "SELL" else -order["volume_left"]
            self._adjust_position(symbol, volume_long_frozen=volume_long, volume_short_frozen=volume_short,
                                  priority=priority)
        else:
            volume_long = 0 if order["direction"] == "SELL" else order["volume_left"]
            volume_short = 0 if order["direction"] == "BUY" else order["volume_left"]
        self._adjust_position(symbol, volume_long=volume_long, volume_short=volume_short, price=price,
                              priority=priority)
        premium = -order["frozen_premium"] if order["direction"] == "BUY" else order["frozen_premium"]
        self._adjust_account(commission=trade["commission"], premium=premium)
        order["volume_left"] = 0
        self._del_order(order, "全部成交")
Esempio n. 10
0
def vwap_table(api: TqApi,
               symbol: str,
               target_pos: int,
               duration: float,
               account: Optional[Union[TqAccount, TqKq, TqSim]] = None):
    """
    返回基于 vwap 策略的计划任务时间表。下单需要配合 TargetPosScheduler 使用。

    调用 vwap_table 函数,根据以下逻辑生成 time_table:

    1. 根据 target_pos - 当前合约的净持仓,得到总的需要调整手数
    2. 请求 symbol 合约的 ``1min`` K 线
    3. 采样取用最近 10 日内,以合约当前行情时间的下一分钟为起点,每日 duration / 60 根 K 线, \
    例如当前合约时间为 14:35:35,那么采样是会使用 14:36:00 开始的分钟线 K 线
    4. 按日期分组,分别计算交易日内,每根 K 线成交量占总成交量的比例
    5. 计算最近 10 日内相同分钟内的成交量占比的算术平均数,将第 1 步得到的总调整手数按照得到的比例分配
    6. 每一分钟,前 58s 以追加价格下单,后 2s 以对价价格下单

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

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

        target_pos (int): 目标持仓手数

        duration (int): 算法执行的时长,以秒为单位,必须是 60 的整数倍,时长可以跨非交易时间段,但是不可以跨交易日
        * 设置为 60*10, 可以是 10:10~10:15 + 10:30~10:35

        account (TqAccount/TqKq/TqSim): [可选]指定发送下单指令的账户实例, 多账户模式下,该参数必须指定

    Returns:
        pandas.DataFrame: 本函数返回一个 pandas.DataFrame 实例. 表示一份计划任务时间表。每一行表示一项目标持仓任务,包含以下列:

            + interval: 当前这项任务的持续时间长度,单位为秒
            + target_pos: 当前这项任务的目标持仓
            + price: 当前这项任务的下单价格模式,支持 PASSIVE(排队价),ACTIVE(对价),None(不下单,表示暂停一段时间)

    Example1::

        from tqsdk import TqApi, TargetPosScheduler
        from tqsdk.algorithm import vwap_table

        api = TqApi(auth="信易账户,用户密码")
        quote = api.get_quote("CZCE.MA109")

        # 设置 vwap 任务参数
        time_table = vwap_table(api, "CZCE.MA109", -100, 600)  # 目标持仓 -100 手,600s 内完成
        print(time_table.to_string())

        target_pos_sch = TargetPosScheduler(api, "CZCE.MA109", time_table)
        # 启动循环
        while not target_pos_sch.is_finished():
            api.wait_update()
        api.close()


    """
    account = api._account._check_valid(account)
    if account is None:
        raise Exception(f"多账户模式下, 需要指定账户实例 account")

    TIME_CELL = 60  # 等时长下单的时间单元, 单位: 秒
    HISTORY_DAY_LENGTH = 10  # 使用多少天的历史数据用来计算每个时间单元的下单手数

    if duration % TIME_CELL or duration < 60:
        raise Exception(f"duration {duration} 参数应该为 {TIME_CELL} 的整数倍")

    pos = account.get_position(symbol)
    target_pos = int(target_pos)
    delta_pos = target_pos - pos.pos
    target_volume = abs(delta_pos)  # 总的下单手数
    if target_volume == 0:
        return DataFrame(columns=['interval', 'target_pos', 'price'])

    # 获取 Kline
    klines = api.get_kline_serial(symbol,
                                  TIME_CELL,
                                  data_length=int(10 * 60 * 60 / TIME_CELL *
                                                  HISTORY_DAY_LENGTH))
    klines["time"] = klines.datetime.apply(
        lambda x: datetime.fromtimestamp(x // 1000000000).time())  # k线时间
    klines["date"] = klines.datetime.apply(lambda x: datetime.fromtimestamp(
        _get_trading_day_from_timestamp(x) // 1000000000).date())  # k线交易日

    quote = api.get_quote(symbol)
    # 当前交易日完整的交易时间段
    trading_timestamp = _get_trading_timestamp(quote, quote.datetime)
    trading_timestamp_nano_range = trading_timestamp[
        'night'] + trading_timestamp['day']  # 当前交易日完整的交易时间段
    # 当前时间 行情时间
    current_timestamp_nano = _get_trade_timestamp(quote.datetime, float('nan'))
    if not trading_timestamp_nano_range[0][
            0] <= current_timestamp_nano < trading_timestamp_nano_range[-1][1]:
        raise Exception("当前时间不在指定的交易时间段内")

    current_datetime = datetime.fromtimestamp(current_timestamp_nano //
                                              1000000000)
    # 下一分钟的开始时间
    next_datetime = current_datetime.replace(second=0) + timedelta(minutes=1)
    start_datetime_nano = int(next_datetime.timestamp()) * 1000000000
    r = _rangeset_head(
        _rangeset_slice(trading_timestamp_nano_range, start_datetime_nano),
        int(duration * 1e9))
    if not (r and trading_timestamp_nano_range[0][0] <= r[-1][-1] <
            trading_timestamp_nano_range[-1][1]):
        raise Exception("指定时间段超出当前交易日")

    start_datetime = datetime.fromtimestamp(start_datetime_nano // 1000000000)
    end_datetime = datetime.fromtimestamp((r[-1][-1] - 1) // 1000000000)
    time_slot_start = time(start_datetime.hour,
                           start_datetime.minute)  # 计划交易时段起始时间点
    time_slot_end = time(end_datetime.hour, end_datetime.minute)  # 计划交易时段终点时间点
    if time_slot_end > time_slot_start:  # 判断是否类似 23:00:00 开始, 01:00:00 结束这样跨天的情况
        klines = klines[(klines["time"] >= time_slot_start)
                        & (klines["time"] <= time_slot_end)]
    else:
        klines = klines[(klines["time"] >= time_slot_start) |
                        (klines["time"] <= time_slot_end)]

    # 获取在预设交易时间段内的所有K线, 即时间位于 time_slot_start 到 time_slot_end 之间的数据
    need_date = klines['date'].drop_duplicates()[-HISTORY_DAY_LENGTH:]
    klines = klines[klines['date'].isin(need_date)]

    grouped_datetime = klines.groupby(['date', 'time'])['volume'].sum()
    # 计算每个交易日内的预设交易时间段内的成交量总和(level=0: 表示按第一级索引"data"来分组)后,将每根k线的成交量除以所在交易日内的总成交量,计算其所占比例
    volume_percent = grouped_datetime / grouped_datetime.groupby(level=0).sum()
    predicted_percent = volume_percent.groupby(
        level=1).mean()  # 将历史上相同时间单元的成交量占比使用算数平均计算出预测值

    # 计算每个时间单元的成交量预测值
    time_table = DataFrame(columns=['interval', 'volume', 'price'])
    volume_left = target_volume  # 剩余手数
    percent_left = 1  # 剩余百分比
    for index, value in predicted_percent.items():
        volume = round(target_volume * (value / percent_left))
        volume_left -= volume
        percent_left -= value
        append_time_table = pd.DataFrame([{
            "interval": TIME_CELL - 2,
            "volume": volume,
            "price": "PASSIVE"
        }, {
            "interval": 2,
            "volume": 0,
            "price": "ACTIVE"
        }])
        time_table = pd.concat([time_table, append_time_table],
                               ignore_index=True)

    time_table['volume'] = time_table['volume'].mul(np.sign(delta_pos))
    time_table['target_pos'] = time_table['volume'].cumsum()
    time_table['target_pos'] = time_table['target_pos'].add(pos.pos)
    time_table.drop(columns=['volume'], inplace=True)
    time_table = time_table.astype({
        'target_pos': 'int64',
        'interval': 'float64'
    })
    return time_table