《选股因子系列研究(八十五)——买卖单主动成交中的隐藏信息》总结

好的,我们来整理一下这份海通证券的研报。

目录

  1. 报告来源与标题
  2. 报告核心观点总结
  3. 策略核心逻辑与因子构建
  4. Python 核心代码示例
    • 4.1 模拟输入数据
    • 4.2 因子计算核心函数
    • 4.3 示例调用
  5. 报告关键结果与回测表现
  6. 指数增强测试与风险提示

1. 报告来源与标题

  • 来源: 海通证券 (Haitong Securities)
  • 标题: 选股因子系列研究(八十五)——买卖单主动成交中的隐藏信息
  • 日期: 2022年10月24日
  • 作者: 冯佳睿, 袁林青

2. 报告核心观点总结

该报告深入研究了基于逐笔成交数据还原出的买卖单信息,重点关注了订单的主动成交度(即订单金额中主动吃掉对手盘挂单的比例)。与前期研究不同,该报告不仅区分了大、中、小单,还计算了各类订单的主动成交度,并发现小单主动成交度具有显著的选股能力。

核心发现包括:

  1. 小单主动成交度因子表现突出: 无论是小买单还是小卖单,其主动成交度越高,股票未来超额收益表现越好。其中,小买单主动成交度因子的月度选股能力略强(月均IC约0.05),多空收益稳健(月度多空收益1.78%,月均多头超额0.80%)。
  2. 盘中数据更优: 使用盘中(10:00-15:00)数据计算的小买单主动成交度因子,选股能力比使用全天或开盘后数据更强。
  3. 正交后依然有效: 在对行业、市值、换手率、反转等常见风格因子进行正交处理后,小买单主动成交度因子(全天和盘中)依然保持显著的选股能力,尽管多空收益有所下滑。
  4. 不同股票池表现: 原始因子在中证800指数成分股以外的股票池中IC更高,选股能力更强。正交后,因子在不同股票池(中证800内/外)的选股能力差距明显缩小,但在中证800外的多头超额收益依然更好。周度调仓下结论类似。
  5. 指数增强有效性: 在不包含深度学习高频因子的基础模型中,将小买单主动成交度因子加入中证500和中证1000指数增强组合,能够带来0.5%-1.5%(CSI 500)或0.5%-2.0%(CSI 1000)的年化超额收益提升。但若基础模型已包含深度学习因子,则该因子的增量贡献不稳定,依赖于具体的风控模型参数。

3. 策略核心逻辑与因子构建

策略的核心是构建并利用“小单主动成交度”因子进行选股。

  1. 数据基础: 使用逐笔成交数据(Tick Data),包含时间、价格、成交量、成交金额、买卖订单号(Buy/Sell Order ID)、以及成交方向标识(BS Flag,B代表主动买,S代表主动卖)。
  2. 订单还原: 通过聚合相同买单号或卖单号的逐笔成交记录,还原出每个独立的买单或卖单的总成交金额。
  3. 订单规模划分(大/中/小单):
    • 对每只股票,计算其过去20个交易日还原后订单金额的滚动均值(Mean)和标准差(Std)。(注意:原文此处描述与表述“基于个股20日成交分布滚动计算”略有模糊,这里理解为基于还原后的订单金额分布)。
    • 大单 (Large): 订单总金额 > Mean + 1 * Std
    • 小单 (Small): 订单总金额 < Mean
    • 中单 (Medium): Mean <= 订单总金额 <= Mean + 1 * Std
  4. 主动成交金额计算:
    • 对于一个还原后的买单,其主动成交金额是所有构成该订单且BS Flag为 ‘B’ 的逐笔成交记录的金额之和。
    • 对于一个还原后的卖单,其主动成交金额是所有构成该订单且BS Flag为 ‘S’ 的逐笔成交记录的金额之和。
  5. 主动成交度计算:
    • 对每个股票每日,分别计算所有小买单、小卖单(以及中单、大单)的主动成交金额之和与总成交金额之和。
    • 小买单主动成交度 = Sum(小买单主动成交金额) / Sum(小买单总成交金额)
    • 小卖单主动成交度 = Sum(小卖单主动成交金额) / Sum(小卖单总成交金额)
    • (报告重点关注小买单主动成交度)
  6. 因子生成:
    • 计算每日的“小买单主动成交度”。
    • 使用多日滚动平均(报告未明确具体天数,常用如5日、10日、20日)平滑日度因子值,得到最终用于选股的因子。
    • (可选但推荐)正交化: 将滚动平滑后的因子对行业、市值、换手率、反转等因子进行回归取残差,以剔除风格影响。
  7. 策略应用:
    • 在每个调仓期(如月末、周末),计算所有股票的最新因子值。
    • 根据因子值对股票进行排序。
    • 构建投资组合,例如买入因子值最高的 N% 的股票(多头),卖出因子值最低的 N% 的股票(空头,如果做多空策略)。或者在指数增强策略中,根据因子值对成分股进行超配或低配。

4. Python 核心代码示例

以下代码提供了一个简化的核心因子计算逻辑的Python示例。请注意: 这段代码是示意性的,需要真实的、处理好的逐笔数据才能运行,并且省略了数据获取、清洗、完整的滚动计算、正交化和回测框架。

import pandas as pd
import numpy as np

# --- 4.1 模拟输入数据 ---
# 假设我们有处理好的某只股票几天的逐笔数据 DataFrame
# 实际应用中需要读取真实的逐笔数据
def simulate_tick_data(days=5, num_trades_per_day=1000):
    """模拟生成一只股票几天的逐笔数据"""
    all_ticks = []
    base_price = 10.0
    order_id_counter = 1
    trade_id_counter = 1

    for day in range(days):
        date_str = f"2023-01-{day+1:02d}"
        day_ticks = []
        # 模拟一些大中小订单
        num_large = 5
        num_medium = 20
        num_small = 50

        for _ in range(num_large): # 大买单
            total_amount = np.random.uniform(80000, 150000)
            num_ticks_order = np.random.randint(5, 15)
            buy_ord_id = order_id_counter
            order_id_counter += 1
            active_pct = np.random.uniform(0.6, 0.95) # 大单主动性高
            active_amount = total_amount * active_pct
            passive_amount = total_amount * (1 - active_pct)
            current_amount = 0
            for i in range(num_ticks_order):
                tick_amount = total_amount / num_ticks_order # 简化处理
                price = base_price + np.random.normal(0, 0.02)
                volume = tick_amount / price
                bs_flag = 'B' if current_amount < active_amount else 'S' # 模拟主动/被动成交
                sell_ord_id = order_id_counter # 分配对手单号
                order_id_counter += 1
                day_ticks.append([date_str, trade_id_counter, price, volume, tick_amount, buy_ord_id, sell_ord_id, bs_flag])
                trade_id_counter += 1
                current_amount += tick_amount

        # 简化模拟中/小单过程... (为简洁起见,下面只用随机生成ticks)
        for i in range(num_trades_per_day - num_large * 10): # 简化模拟,非精确
             price = base_price + np.random.normal(0, 0.02)
             tick_amount = np.random.uniform(100, 50000) # 覆盖大中小
             volume = tick_amount / price
             buy_ord_id = order_id_counter + np.random.randint(0, 50)
             sell_ord_id = order_id_counter + np.random.randint(50, 100)
             bs_flag = np.random.choice(['B', 'S'])
             day_ticks.append([date_str, trade_id_counter, price, volume, tick_amount, buy_ord_id, sell_ord_id, bs_flag])
             trade_id_counter += 1
        order_id_counter += 100 # 增加order id计数器

        all_ticks.extend(day_ticks)
        base_price *= (1 + np.random.normal(0, 0.01)) # 价格日间波动

    ticks_df = pd.DataFrame(all_ticks, columns=['date', 'trade_id', 'price', 'volume', 'amount', 'buy_order_id', 'sell_order_id', 'bs_flag'])
    ticks_df['date'] = pd.to_datetime(ticks_df['date'])
    # 确保ID是整数类型,便于分组
    ticks_df[['buy_order_id', 'sell_order_id']] = ticks_df[['buy_order_id', 'sell_order_id']].astype(int)
    return ticks_df

# --- 4.2 因子计算核心函数 ---
def calculate_small_order_activity_factor(ticks_df, rolling_window=20, factor_smooth_window=5):
    """
    计算小买单主动成交度因子 (示意性,非完整回测)
    Args:
        ticks_df (pd.DataFrame): 包含多日单只股票的逐笔数据
        rolling_window (int): 计算订单金额分布的滚动窗口
        factor_smooth_window (int): 对最终日度因子进行平滑的窗口
    Returns:
        pd.DataFrame: 包含每日因子值的DataFrame
    """
    daily_factors = {}
    all_order_amounts = {} # 存储历史订单金额用于计算滚动阈值

    # 确保按日期排序
    ticks_df = ticks_df.sort_values(by='date')
    dates = ticks_df['date'].unique()

    for current_date in dates:
        print(f"Processing date: {current_date.date()}")
        daily_ticks = ticks_df[ticks_df['date'] == current_date].copy()
        if daily_ticks.empty:
            continue

        # 1. 订单还原与主动成交额计算
        buy_orders = daily_ticks.groupby('buy_order_id').agg(
            total_amount=('amount', 'sum'),
            active_amount=('amount', lambda x: x[daily_ticks.loc[x.index, 'bs_flag'] == 'B'].sum()) # 主动买入金额
        ).reset_index().rename(columns={'buy_order_id': 'order_id'})
        buy_orders['order_type'] = 'buy'

        sell_orders = daily_ticks.groupby('sell_order_id').agg(
            total_amount=('amount', 'sum'),
            active_amount=('amount', lambda x: x[daily_ticks.loc[x.index, 'bs_flag'] == 'S'].sum()) # 主动卖出金额
        ).reset_index().rename(columns={'sell_order_id': 'order_id'})
        sell_orders['order_type'] = 'sell'

        reconstructed_orders = pd.concat([buy_orders, sell_orders], ignore_index=True)
        reconstructed_orders = reconstructed_orders[reconstructed_orders['total_amount'] > 0] # 过滤掉无效订单

        # 存储当日订单金额用于未来滚动计算
        all_order_amounts[current_date] = reconstructed_orders['total_amount'].copy()

        # 2. 计算订单规模阈值 (需要历史数据)
        # 获取用于计算阈值的历史日期范围
        start_date_threshold = current_date - pd.Timedelta(days=rolling_window * 2) # 넉넉하게 과거 데이터 선택
        relevant_dates = sorted([d for d in all_order_amounts.keys() if d < current_date and d >= start_date_threshold])
        # 取最近rolling_window个交易日的数据
        hist_dates_for_threshold = relevant_dates[-(rolling_window-1):] if len(relevant_dates) >= rolling_window-1 else relevant_dates

        if not hist_dates_for_threshold:
             print(f"  Skipping threshold calculation for {current_date.date()} due to insufficient history.")
             daily_factors[current_date] = np.nan # 历史数据不足,无法计算因子
             continue

        # 合并历史订单金额
        hist_amounts = pd.concat([all_order_amounts[d] for d in hist_dates_for_threshold] + [all_order_amounts[current_date]], ignore_index=True)

        if hist_amounts.empty:
            print(f"  Skipping threshold calculation for {current_date.date()} due to empty historical amounts.")
            daily_factors[current_date] = np.nan
            continue

        # 计算均值和标准差
        mean_amount = hist_amounts.mean()
        std_amount = hist_amounts.std()

        # 定义阈值
        threshold_small = mean_amount
        threshold_large = mean_amount + 1 * std_amount

        # 3. 订单分类
        def classify_order(amount):
            if amount < threshold_small:
                return 'Small'
            elif amount > threshold_large:
                return 'Large'
            else:
                return 'Medium'

        reconstructed_orders['size_category'] = reconstructed_orders['total_amount'].apply(classify_order)

        # 4. 计算小买单主动成交度
        small_buy_orders = reconstructed_orders[(reconstructed_orders['order_type'] == 'buy') & (reconstructed_orders['size_category'] == 'Small')]

        total_small_buy_amount = small_buy_orders['total_amount'].sum()
        active_small_buy_amount = small_buy_orders['active_amount'].sum()

        if total_small_buy_amount > 0:
            small_buy_activity_degree = active_small_buy_amount / total_small_buy_amount
        else:
            small_buy_activity_degree = np.nan # 当天没有小买单

        daily_factors[current_date] = small_buy_activity_degree
        print(f"  Date: {current_date.date()}, Small Buy Activity Degree: {small_buy_activity_degree:.4f}, Num Small Buys: {len(small_buy_orders)}")


    # 5. 因子平滑
    factor_series = pd.Series(daily_factors).sort_index()
    smoothed_factor = factor_series.rolling(window=factor_smooth_window, min_periods=max(1, factor_smooth_window // 2)).mean()

    result_df = pd.DataFrame({
        'raw_factor': factor_series,
        'smoothed_factor': smoothed_factor
    })
    return result_df


# --- 4.3 示例调用 ---
if __name__ == "__main__":
    # 模拟数据 (实际应读取数据)
    simulated_ticks = simulate_tick_data(days=30, num_trades_per_day=500) # 模拟30天的数据

    # 计算因子 (对这只模拟股票)
    # 注意:需要足够长的历史数据才能准确计算滚动阈值
    factor_result = calculate_small_order_activity_factor(simulated_ticks, rolling_window=20, factor_smooth_window=5)

    print("\nFactor Calculation Result (Last 10 days):")
    print(factor_result.tail(10))

    # --- 后续步骤 ---
    # 1. 对股票池中所有股票重复以上计算过程
    # 2. (可选) 进行因子正交化处理
    # 3. 使用 smoothed_factor 或 正交化后的因子 进行回测
    #    - 截面排序
    #    - 构建投资组合 (如 Top 10% vs Bottom 10%)
    #    - 计算收益率、IC、Sharpe Ratio等指标

代码说明:

  1. simulate_tick_data: 生成用于演示的假数据,实际应用需替换为真实数据接口。
  2. calculate_small_order_activity_factor:
    • 按日处理数据。
    • 使用 groupby() 还原买单和卖单,并计算其总金额和主动成交金额。
    • 存储每日订单金额 all_order_amounts 用于滚动计算历史分布。
    • 根据过去 rolling_window 天的订单金额分布计算均值和标准差,确定大/中/小单阈值。(注意:这里简化处理,实际滚动计算需要更严谨的数据管理)。
    • 对当日订单进行分类。
    • 筛选出小买单,计算总的主动成交额和总成交额,得到日度因子值。
    • 使用 rolling().mean() 对日度因子进行平滑。
  3. if __name__ == "__main__": 部分展示了如何调用函数并打印结果。
  4. 重要: 此代码仅为核心逻辑示意,距离实盘或完整回测相差甚远,缺少错误处理、数据管理、多股票并行、正交化、回测引擎等关键部分。滚动阈值的计算方式也可能需要根据实际数据情况调整。

5. 报告关键结果与回测表现

  • 因子有效性: 小单(尤其小买单)主动成交度因子具有显著且稳健的月度和周度选股能力(正相关)。
  • IC值: 全天小买单主动成交度月均IC为0.050,盘中小买单为0.053。正交后仍保持较高水平(如全天0.032,盘中0.033)。周度IC略低但依然显著。
  • 多空与多头: 因子分组收益单调性好,多空组合表现稳健。全天小买单因子月度多空年化约21%(1.78%*12),多头年化超额约9.6%(0.80%*12)。盘中因子表现更优。正交后收益有所下降但依然可观。
  • 时间段: 因子在2014、2018、2019年表现稍弱,但2020年后逐年增强,尤其在2022年表现突出。正交后年度表现更稳定。
  • 与其他因子相关性: 与市值、反转、换手率、BP等常见因子相关性不高(绝对值多在0.3以下),说明因子具有较好的独立性。与大单买入意愿类因子有一定正相关性。

6. 指数增强测试与风险提示

  • 增强效果:
    • 在中证500和中证1000增强策略中,若不包含深度学习高频因子,引入小买单主动成交度因子能显著提升超额收益(年化提升0.5%-2.0%不等,视约束条件而定)。
    • 若基础模型已包含深度学习高频因子,该因子的增量效果不确定,有时甚至可能不提升或略微降低收益,具体效果依赖于风控模型(如个股偏离、行业偏离、因子敞口约束等)的选择。
  • 风险提示:
    • 市场系统性风险。
    • 资产流动性风险(尤其小市值股票)。
    • 政策变动风险。
    • 模型失效风险(因子在未来可能失效)。
    • 数据噪声风险(逐笔数据质量影响)。

希望这份总结和代码示例对您理解报告内容有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值