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 "全部成交"
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)
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
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
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 "下单失败, 不在可交易时间段内"
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
def _get_trade_timestamp(self): return _get_trade_timestamp(self._current_datetime, self._local_time_record)
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, "全部成交")
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