Skip to content

7.5 事件研究方法(Event Study Methodology)

"Event studies are a way of measuring the impact of an economic event on the value of a firm.""事件研究是衡量经济事件对企业价值影响的一种方法。"— Eugene Fama, 2013 Nobel Laureate in Economics (2013年诺贝尔经济学奖得主)

评估特定事件的因果效应:从金融市场到政策评估

难度重要性


本节目标

完成本节后,你将能够:

  • 理解事件研究方法的基本框架和应用场景
  • 掌握异常收益(AR)和累积异常收益(CAR)的计算
  • 建立市场模型估计正常收益
  • 进行统计显著性检验(t检验、横截面检验)
  • 实施完整的事件研究分析流程
  • 评估并购公告、政策变化等事件的影响
  • 使用 Python 完整实现事件研究

什么是事件研究?

核心思想

事件研究(Event Study):评估特定事件对某个变量(通常是股票收益率)的影响。

基本逻辑

  1. 如果事件没有发生,结果变量会遵循"正常"模式
  2. 事件发生后,观察到的实际值与"正常"预期值之间的差异就是事件效应
  3. 这种差异被称为异常收益(Abnormal Return)

数学表达

经典应用领域

领域典型事件研究问题
金融市场并购公告、盈利公告、股票分拆事件是否影响股价?
公司治理CEO 变更、董事会改组治理变化如何影响公司价值?
监管政策新法规颁布、政策变更监管对市场的影响?
宏观经济央行降息、财政刺激政策对资产价格的影响?
公共政策教育改革、环境法规政策对社会经济指标的影响?

奠基性研究:Fama et al. (1969)

论文The Adjustment of Stock Prices to New Information

研究问题:股票分拆(stock splits)如何影响股价?

发现

  • 股票分拆公告前,股价有显著异常收益(30个月内约33%)
  • 分拆公告后,异常收益消失
  • 结论:市场是有效的,股价快速反映新信息

影响

  • 开创了事件研究方法论
  • 为有效市场假说提供实证支持
  • 成为金融经济学最常用的方法之一

事件研究的基本框架

时间轴设计

时间轴
├── 估计窗口(Estimation Window)
│   ├── T0 到 T1(通常为 120-250 天)
│   └── 目的:估计"正常"收益模型参数

├── 事件窗口(Event Window)
│   ├── T1 到 T2(通常为事件前后 -10 到 +10 天)
│   ├── 事件日:t = 0
│   └── 目的:计算异常收益

└── 事后窗口(Post-Event Window)
    ├── T2 到 T3(可选)
    └── 目的:评估长期效应

符号定义

  • :估计窗口起点
  • :估计窗口终点 = 事件窗口起点
  • :事件窗口终点
  • :事后窗口终点
  • :事件日

典型参数选择

  • 估计窗口:(事件前250到11个交易日)
  • 事件窗口:(事件前后各10个交易日)

可视化事件窗口

python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 设置
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")

# 时间轴示意图
fig, ax = plt.subplots(figsize=(16, 6))

# 估计窗口
ax.barh(0, 239, left=-250, height=0.3, color='blue', alpha=0.3, label='估计窗口')
ax.text(-130, 0, '估计窗口\n(Estimation Window)\nT₀=-250 到 T₁=-11',
        ha='center', va='center', fontsize=12, fontweight='bold')

# 事件窗口
ax.barh(0, 21, left=-10, height=0.3, color='red', alpha=0.5, label='事件窗口')
ax.text(0, 0, '事件窗口\n(Event Window)\nT₁=-10 到 T₂=+10',
        ha='center', va='center', fontsize=12, fontweight='bold')

# 事件日
ax.axvline(x=0, color='darkred', linestyle='--', linewidth=3, label='事件日 (t=0)')
ax.scatter([0], [0], s=300, c='red', marker='*', zorder=10, edgecolors='black', linewidths=2)

# 事后窗口
ax.barh(0, 89, left=11, height=0.3, color='green', alpha=0.3, label='事后窗口')
ax.text(55, 0, '事后窗口\n(Post-Event)\nT₂=+11 到 T₃=+100',
        ha='center', va='center', fontsize=12, fontweight='bold')

# 装饰
ax.set_xlim(-260, 110)
ax.set_ylim(-0.5, 0.5)
ax.set_xlabel('相对事件日的交易日 (t)', fontsize=14, fontweight='bold')
ax.set_yticks([])
ax.legend(loc='upper right', fontsize=11)
ax.set_title('事件研究时间轴设计', fontsize=16, fontweight='bold', pad=20)
ax.grid(True, alpha=0.3, axis='x')

plt.tight_layout()
plt.savefig('event_study_timeline.png', dpi=300, bbox_inches='tight')
plt.show()

步骤 1:正常收益模型

常用正常收益模型

1. 市场模型(Market Model)⭐ 最常用

定义

其中:

  • :股票 时刻的收益率
  • :市场组合在 时刻的收益率(如沪深300指数)
  • :通过估计窗口数据估计的参数

优势

  • 考虑了市场整体波动
  • 消除了系统性风险
  • 估计简单,解释直观

2. 均值调整模型(Mean-Adjusted Return Model)

定义

其中 是股票 在估计窗口的平均收益率。

正常收益预期

优势:简单,适用于数据有限的情况

劣势:未考虑市场整体波动

3. 市场调整模型(Market-Adjusted Return Model)

定义

假设所有股票的预期收益等于市场收益()。

优势:无需估计参数

劣势:假设过于简化

模型对比

模型参数优点缺点适用场景
市场模型考虑市场风险需要估计窗口⭐ 标准选择
均值调整简单直观忽略市场波动数据有限
市场调整无需估计假设太强快速分析

步骤 2:计算异常收益(AR)

异常收益的定义

异常收益(Abnormal Return, AR)

使用市场模型

其中 是在估计窗口通过 OLS 估计的参数。

Python 实现:计算单个股票的 AR

python
import numpy as np
import pandas as pd
import statsmodels.api as sm
from datetime import datetime, timedelta

class EventStudy:
    """事件研究完整流程"""

    def __init__(self, stock_returns, market_returns, event_date,
                 estimation_window=(-250, -11), event_window=(-10, 10)):
        """
        参数:
        ------
        stock_returns : pd.Series
            股票日收益率(索引为日期)
        market_returns : pd.Series
            市场日收益率(索引为日期)
        event_date : str or datetime
            事件日期
        estimation_window : tuple
            估计窗口 (起点, 终点),相对于事件日
        event_window : tuple
            事件窗口 (起点, 终点),相对于事件日
        """
        self.stock_returns = stock_returns
        self.market_returns = market_returns
        self.event_date = pd.to_datetime(event_date)
        self.estimation_window = estimation_window
        self.event_window = event_window

        # 对齐数据
        self.data = pd.DataFrame({
            'stock': stock_returns,
            'market': market_returns
        }).dropna()

        # 创建事件时间索引
        self.create_event_time()

    def create_event_time(self):
        """创建相对事件日的时间索引"""
        self.data['date'] = self.data.index

        # 找到事件日在数据中的位置
        event_loc = self.data.index.get_loc(self.event_date, method='nearest')

        # 创建事件时间(相对天数)
        self.data['event_time'] = range(-event_loc, len(self.data) - event_loc)

        print(f"事件日期: {self.event_date.strftime('%Y-%m-%d')}")
        print(f"事件日在数据中的位置: 第 {event_loc} 个交易日")

    def estimate_market_model(self):
        """步骤1: 估计市场模型"""
        print("\n" + "="*70)
        print("步骤 1: 估计市场模型")
        print("="*70)

        # 选择估计窗口数据
        est_start, est_end = self.estimation_window
        estimation_data = self.data[
            (self.data['event_time'] >= est_start) &
            (self.data['event_time'] <= est_end)
        ].copy()

        print(f"\n估计窗口: [{est_start}, {est_end}] (共 {len(estimation_data)} 个交易日)")
        print(f"日期范围: {estimation_data['date'].iloc[0].strftime('%Y-%m-%d')} 到 "
              f"{estimation_data['date'].iloc[-1].strftime('%Y-%m-%d')}")

        # OLS 回归: R_stock = alpha + beta * R_market + epsilon
        X = sm.add_constant(estimation_data['market'])
        model = sm.OLS(estimation_data['stock'], X).fit()

        self.alpha = model.params['const']
        self.beta = model.params['market']
        self.model = model

        print(f"\n市场模型估计结果:")
        print(f"  α (截距): {self.alpha:.6f} (p-value: {model.pvalues['const']:.4f})")
        print(f"  β (市场风险): {self.beta:.6f} (p-value: {model.pvalues['market']:.4f})")
        print(f"  R²: {model.rsquared:.4f}")
        print(f"  残差标准差: {np.sqrt(model.mse_resid):.6f}")

        return self.alpha, self.beta

    def calculate_abnormal_returns(self):
        """步骤2: 计算异常收益"""
        print("\n" + "="*70)
        print("步骤 2: 计算异常收益(AR)")
        print("="*70)

        # 预测正常收益
        self.data['normal_return'] = self.alpha + self.beta * self.data['market']

        # 异常收益 = 实际收益 - 正常收益
        self.data['AR'] = self.data['stock'] - self.data['normal_return']

        # 选择事件窗口
        evt_start, evt_end = self.event_window
        event_data = self.data[
            (self.data['event_time'] >= evt_start) &
            (self.data['event_time'] <= evt_end)
        ].copy()

        print(f"\n事件窗口: [{evt_start}, {evt_end}] (共 {len(event_data)} 个交易日)")
        print(f"日期范围: {event_data['date'].iloc[0].strftime('%Y-%m-%d')} 到 "
              f"{event_data['date'].iloc[-1].strftime('%Y-%m-%d')}")

        print(f"\n事件窗口异常收益统计:")
        print(f"  平均 AR: {event_data['AR'].mean():.4%}")
        print(f"  标准差: {event_data['AR'].std():.4%}")
        print(f"  最小值: {event_data['AR'].min():.4%} (t={event_data.loc[event_data['AR'].idxmin(), 'event_time']:.0f})")
        print(f"  最大值: {event_data['AR'].max():.4%} (t={event_data.loc[event_data['AR'].idxmax(), 'event_time']:.0f})")

        # 事件日异常收益
        event_day_ar = event_data[event_data['event_time'] == 0]['AR'].values[0]
        print(f"\n事件日 (t=0) 异常收益: {event_day_ar:.4%}")

        return self.data

    def calculate_CAR(self, window=None):
        """步骤3: 计算累积异常收益(CAR)"""
        print("\n" + "="*70)
        print("步骤 3: 计算累积异常收益(CAR)")
        print("="*70)

        if window is None:
            window = self.event_window

        evt_start, evt_end = window
        event_data = self.data[
            (self.data['event_time'] >= evt_start) &
            (self.data['event_time'] <= evt_end)
        ].copy()

        # 计算累积异常收益
        event_data['CAR'] = event_data['AR'].cumsum()

        # 保存到主数据框
        self.data.loc[event_data.index, 'CAR'] = event_data['CAR']

        total_car = event_data['CAR'].iloc[-1]
        print(f"\n窗口 [{evt_start}, {evt_end}] 的累积异常收益:")
        print(f"  CAR({evt_start}, {evt_end}) = {total_car:.4%}")

        # 不同窗口的 CAR
        print(f"\n不同窗口的 CAR:")
        for window_name, (start, end) in [
            ('事件前', (evt_start, -1)),
            ('事件日', (0, 0)),
            ('事件后', (1, evt_end)),
            ('事件日±1', (-1, 1)),
            ('全窗口', (evt_start, evt_end))
        ]:
            window_data = event_data[
                (event_data['event_time'] >= start) &
                (event_data['event_time'] <= end)
            ]
            car = window_data['AR'].sum()
            print(f"  {window_name:8s} [{start:3d}, {end:3d}]: {car:8.4%}")

        return event_data

    def test_significance(self, test_window=(-10, 10)):
        """步骤4: 统计显著性检验"""
        print("\n" + "="*70)
        print("步骤 4: 统计显著性检验")
        print("="*70)

        # 估计窗口的残差标准差
        est_start, est_end = self.estimation_window
        estimation_data = self.data[
            (self.data['event_time'] >= est_start) &
            (self.data['event_time'] <= est_end)
        ]
        sigma_epsilon = np.sqrt(self.model.mse_resid)

        # 事件窗口数据
        evt_start, evt_end = test_window
        event_data = self.data[
            (self.data['event_time'] >= evt_start) &
            (self.data['event_time'] <= evt_end)
        ].copy()

        # t 检验:单个 AR 的显著性
        event_data['t_stat'] = event_data['AR'] / sigma_epsilon
        event_data['p_value'] = 2 * (1 - stats.norm.cdf(np.abs(event_data['t_stat'])))

        print(f"\n1. 单日异常收益 t 检验(H₀: AR = 0)")
        print(f"   检验窗口: [{evt_start}, {evt_end}]")
        print(f"   残差标准差 (σ_ε): {sigma_epsilon:.6f}")

        # 显著的 AR
        significant_ar = event_data[event_data['p_value'] < 0.05]
        print(f"\n   显著异常收益 (p < 0.05): {len(significant_ar)} 个交易日")
        if len(significant_ar) > 0:
            print(f"\n   {'日期':>12s} {'t':>5s} {'AR':>10s} {'t统计量':>10s} {'p值':>10s}")
            print("   " + "-"*50)
            for idx, row in significant_ar.iterrows():
                print(f"   {row['date'].strftime('%Y-%m-%d'):>12s} "
                      f"{row['event_time']:>5.0f} "
                      f"{row['AR']:>10.4%} "
                      f"{row['t_stat']:>10.4f} "
                      f"{row['p_value']:>10.4f}")

        # CAR t 检验
        L = len(event_data)
        car = event_data['AR'].sum()
        sigma_car = sigma_epsilon * np.sqrt(L)
        t_stat_car = car / sigma_car
        p_value_car = 2 * (1 - stats.norm.cdf(np.abs(t_stat_car)))

        print(f"\n2. 累积异常收益 CAR 检验(H₀: CAR = 0)")
        print(f"   窗口长度 (L): {L} 个交易日")
        print(f"   CAR({evt_start}, {evt_end}): {car:.4%}")
        print(f"   标准差 (σ_CAR = σ_ε × √L): {sigma_car:.6f}")
        print(f"   t 统计量: {t_stat_car:.4f}")
        print(f"   p 值: {p_value_car:.4f}")

        if p_value_car < 0.01:
            print(f"   结论: CAR 在 1% 水平显著 ***")
        elif p_value_car < 0.05:
            print(f"   结论: CAR 在 5% 水平显著 **")
        elif p_value_car < 0.10:
            print(f"   结论: CAR 在 10% 水平显著 *")
        else:
            print(f"   结论: CAR 不显著")

        return event_data, t_stat_car, p_value_car

    def plot_results(self):
        """可视化事件研究结果"""
        print("\n" + "="*70)
        print("步骤 5: 可视化结果")
        print("="*70)

        # 选择绘图窗口
        plot_start = max(self.event_window[0] - 5, self.estimation_window[0])
        plot_end = self.event_window[1] + 5

        plot_data = self.data[
            (self.data['event_time'] >= plot_start) &
            (self.data['event_time'] <= plot_end)
        ].copy()

        fig, axes = plt.subplots(3, 1, figsize=(14, 12))

        # 图1: 股票收益 vs 市场收益
        axes[0].plot(plot_data['event_time'], plot_data['stock'],
                     linewidth=1.5, label='股票收益', marker='o', markersize=3)
        axes[0].plot(plot_data['event_time'], plot_data['normal_return'],
                     linewidth=1.5, label='正常收益(市场模型预测)',
                     linestyle='--', alpha=0.7)
        axes[0].axvline(x=0, color='red', linestyle='--', linewidth=2,
                       alpha=0.7, label='事件日')
        axes[0].axhline(y=0, color='black', linestyle='-', alpha=0.3, linewidth=0.8)
        axes[0].fill_between(plot_data['event_time'],
                            plot_data['stock'],
                            plot_data['normal_return'],
                            alpha=0.2, color='purple', label='异常收益')
        axes[0].set_title('股票收益 vs 正常收益(市场模型)',
                         fontsize=14, fontweight='bold')
        axes[0].set_xlabel('相对事件日的交易日 (t)')
        axes[0].set_ylabel('收益率')
        axes[0].legend(loc='best')
        axes[0].grid(True, alpha=0.3)

        # 图2: 异常收益(AR)
        colors = ['red' if ar > 0 else 'green' for ar in plot_data['AR']]
        axes[1].bar(plot_data['event_time'], plot_data['AR'],
                   color=colors, alpha=0.6, edgecolor='black', linewidth=0.5)
        axes[1].axvline(x=0, color='red', linestyle='--', linewidth=2,
                       alpha=0.7, label='事件日')
        axes[1].axhline(y=0, color='black', linestyle='-', alpha=0.5, linewidth=1)
        axes[1].set_title('异常收益(AR)', fontsize=14, fontweight='bold')
        axes[1].set_xlabel('相对事件日的交易日 (t)')
        axes[1].set_ylabel('异常收益')
        axes[1].legend(loc='best')
        axes[1].grid(True, alpha=0.3)

        # 图3: 累积异常收益(CAR)
        event_window_data = plot_data[
            (plot_data['event_time'] >= self.event_window[0]) &
            (plot_data['event_time'] <= self.event_window[1])
        ]
        axes[2].plot(event_window_data['event_time'],
                    event_window_data['CAR'],
                    linewidth=2.5, color='darkblue', marker='o', markersize=5,
                    label='累积异常收益(CAR)')
        axes[2].axvline(x=0, color='red', linestyle='--', linewidth=2,
                       alpha=0.7, label='事件日')
        axes[2].axhline(y=0, color='black', linestyle='-', alpha=0.5, linewidth=1)
        axes[2].fill_between(event_window_data['event_time'],
                            0, event_window_data['CAR'],
                            alpha=0.3, color='blue')

        # 标注最终 CAR
        final_car = event_window_data['CAR'].iloc[-1]
        axes[2].annotate(f'CAR = {final_car:.2%}',
                        xy=(event_window_data['event_time'].iloc[-1], final_car),
                        xytext=(10, 10), textcoords='offset points',
                        fontsize=12, fontweight='bold',
                        bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5),
                        arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'))

        axes[2].set_title('累积异常收益(CAR)', fontsize=14, fontweight='bold')
        axes[2].set_xlabel('相对事件日的交易日 (t)')
        axes[2].set_ylabel('累积异常收益')
        axes[2].legend(loc='best')
        axes[2].grid(True, alpha=0.3)

        plt.tight_layout()
        plt.savefig('event_study_results.png', dpi=300, bbox_inches='tight')
        plt.show()

        print("\n图表已生成并保存!")

# 使用示例
print("\n" + "="*70)
print("事件研究完整示例")
print("="*70)

# 模拟数据
from scipy import stats

np.random.seed(2024)
n_days = 400

# 生成日期索引
dates = pd.date_range('2022-01-01', periods=n_days, freq='B')

# 生成市场收益(均值0.05%, 标准差1.5%)
market_returns = np.random.normal(0.0005, 0.015, n_days)

# 生成股票收益(市场模型: alpha=0.03%, beta=1.2)
true_alpha = 0.0003
true_beta = 1.2
epsilon = np.random.normal(0, 0.01, n_days)
stock_returns = true_alpha + true_beta * market_returns + epsilon

# 在事件日添加异常收益(+5%)
event_day_idx = 250  # 事件发生在第250天
stock_returns[event_day_idx] += 0.05

# 在事件后几天也添加一些异常收益(信息逐步释放)
stock_returns[event_day_idx + 1] += 0.02
stock_returns[event_day_idx + 2] += 0.01

# 转为 Series
stock_returns_series = pd.Series(stock_returns, index=dates, name='股票收益')
market_returns_series = pd.Series(market_returns, index=dates, name='市场收益')

# 事件日期
event_date = dates[event_day_idx]

# 初始化事件研究
es = EventStudy(
    stock_returns=stock_returns_series,
    market_returns=market_returns_series,
    event_date=event_date,
    estimation_window=(-250, -11),
    event_window=(-10, 10)
)

# 执行完整流程
alpha_hat, beta_hat = es.estimate_market_model()
data_with_ar = es.calculate_abnormal_returns()
event_data = es.calculate_CAR()
test_results, t_stat, p_val = es.test_significance()
es.plot_results()

多事件研究(Multiple Event Study)

跨截面平均

当我们有多个事件(如多个公司的并购公告)时,需要计算平均异常收益(AAR)平均累积异常收益(CAAR)

平均异常收益(AAR)

其中 是事件数量。

平均累积异常收益(CAAR)

横截面 t 检验

标准误

t 统计量

Python 实现:多事件研究

python
class MultiEventStudy:
    """多事件研究分析"""

    def __init__(self, events_df, estimation_window=(-250, -11),
                 event_window=(-10, 10)):
        """
        参数:
        ------
        events_df : pd.DataFrame
            包含列: ['event_id', 'date', 'stock_return', 'market_return']
            event_id: 事件标识符
            date: 日期
            stock_return: 股票收益率
            market_return: 市场收益率
        """
        self.events_df = events_df
        self.estimation_window = estimation_window
        self.event_window = event_window
        self.results = []

    def run_all_events(self):
        """对所有事件运行事件研究"""
        print("\n" + "="*70)
        print("多事件研究分析")
        print("="*70)

        event_ids = self.events_df['event_id'].unique()
        print(f"\n总事件数: {len(event_ids)}")

        for i, event_id in enumerate(event_ids):
            print(f"\n--- 处理事件 {i+1}/{len(event_ids)}: {event_id} ---")

            # 获取该事件的数据
            event_data = self.events_df[
                self.events_df['event_id'] == event_id
            ].copy()

            # 找到事件日(标记为 event_time = 0)
            event_date = event_data[event_data['event_time'] == 0]['date'].iloc[0]

            stock_ret = event_data.set_index('date')['stock_return']
            market_ret = event_data.set_index('date')['market_return']

            try:
                # 运行单事件研究
                es = EventStudy(
                    stock_returns=stock_ret,
                    market_returns=market_ret,
                    event_date=event_date,
                    estimation_window=self.estimation_window,
                    event_window=self.event_window
                )

                es.estimate_market_model()
                es.calculate_abnormal_returns()
                es.calculate_CAR()

                # 提取事件窗口的 AR 和 CAR
                evt_start, evt_end = self.event_window
                event_window_data = es.data[
                    (es.data['event_time'] >= evt_start) &
                    (es.data['event_time'] <= evt_end)
                ].copy()

                event_window_data['event_id'] = event_id
                self.results.append(event_window_data[
                    ['event_id', 'event_time', 'AR', 'CAR']
                ])

            except Exception as e:
                print(f"  错误: {str(e)}")
                continue

        # 合并所有结果
        self.all_results = pd.concat(self.results, ignore_index=True)
        print(f"\n成功分析 {len(self.results)} 个事件")

    def calculate_AAR_CAAR(self):
        """计算平均异常收益(AAR)和平均累积异常收益(CAAR)"""
        print("\n" + "="*70)
        print("计算平均异常收益(AAR)和平均累积异常收益(CAAR)")
        print("="*70)

        # 按事件时间分组,计算 AAR
        aar_df = self.all_results.groupby('event_time').agg({
            'AR': ['mean', 'std', 'count']
        }).reset_index()

        aar_df.columns = ['event_time', 'AAR', 'std_AR', 'N']

        # 标准误
        aar_df['SE_AAR'] = aar_df['std_AR'] / np.sqrt(aar_df['N'])

        # t 统计量
        aar_df['t_stat'] = aar_df['AAR'] / aar_df['SE_AAR']

        # p 值
        aar_df['p_value'] = 2 * (1 - stats.norm.cdf(np.abs(aar_df['t_stat'])))

        # 显著性标记
        aar_df['sig'] = aar_df['p_value'].apply(
            lambda p: '***' if p < 0.01 else ('**' if p < 0.05 else ('*' if p < 0.10 else ''))
        )

        self.aar_df = aar_df

        # 计算 CAAR
        aar_df['CAAR'] = aar_df['AAR'].cumsum()

        # CAAR 的标准误(假设 AR 不相关)
        aar_df['SE_CAAR'] = np.sqrt(
            (aar_df['std_AR']**2 / aar_df['N']).cumsum()
        )

        # CAAR 的 t 统计量
        aar_df['t_stat_CAAR'] = aar_df['CAAR'] / aar_df['SE_CAAR']

        # CAAR 的 p 值
        aar_df['p_value_CAAR'] = 2 * (1 - stats.norm.cdf(np.abs(aar_df['t_stat_CAAR'])))

        print(f"\n平均异常收益(AAR)统计:")
        print(f"  样本事件数: {aar_df['N'].iloc[0]}")
        print(f"\n  {'日期':>5s} {'AAR':>10s} {'标准误':>10s} {'t统计量':>10s} {'p值':>10s} {'显著性':>5s}")
        print("  " + "-"*60)

        for _, row in aar_df.iterrows():
            print(f"  {row['event_time']:>5.0f} "
                  f"{row['AAR']:>10.4%} "
                  f"{row['SE_AAR']:>10.6f} "
                  f"{row['t_stat']:>10.4f} "
                  f"{row['p_value']:>10.4f} "
                  f"{row['sig']:>5s}")

        # 总 CAAR
        total_caar = aar_df['CAAR'].iloc[-1]
        se_total_caar = aar_df['SE_CAAR'].iloc[-1]
        t_total_caar = aar_df['t_stat_CAAR'].iloc[-1]
        p_total_caar = aar_df['p_value_CAAR'].iloc[-1]

        print(f"\n累积平均异常收益(CAAR):")
        print(f"  CAAR({self.event_window[0]}, {self.event_window[1]}): {total_caar:.4%}")
        print(f"  标准误: {se_total_caar:.6f}")
        print(f"  t 统计量: {t_total_caar:.4f}")
        print(f"  p 值: {p_total_caar:.4f}")

        if p_total_caar < 0.01:
            print(f"  结论: CAAR 在 1% 水平显著 ***")
        elif p_total_caar < 0.05:
            print(f"  结论: CAAR 在 5% 水平显著 **")
        elif p_total_caar < 0.10:
            print(f"  结论: CAAR 在 10% 水平显著 *")
        else:
            print(f"  结论: CAAR 不显著")

        return aar_df

    def plot_AAR_CAAR(self):
        """可视化 AAR 和 CAAR"""
        fig, axes = plt.subplots(2, 1, figsize=(14, 10))

        # 图1: AAR
        axes[0].bar(self.aar_df['event_time'], self.aar_df['AAR'],
                   color=['red' if x > 0 else 'green' for x in self.aar_df['AAR']],
                   alpha=0.6, edgecolor='black', linewidth=0.5)

        # 添加误差线
        axes[0].errorbar(self.aar_df['event_time'], self.aar_df['AAR'],
                        yerr=1.96 * self.aar_df['SE_AAR'],  # 95% 置信区间
                        fmt='none', ecolor='black', capsize=3, alpha=0.5)

        axes[0].axvline(x=0, color='red', linestyle='--', linewidth=2,
                       alpha=0.7, label='事件日')
        axes[0].axhline(y=0, color='black', linestyle='-', alpha=0.5, linewidth=1)

        # 标注显著的 AAR
        sig_aar = self.aar_df[self.aar_df['p_value'] < 0.05]
        for _, row in sig_aar.iterrows():
            axes[0].scatter(row['event_time'], row['AAR'],
                          s=100, c='yellow', marker='*',
                          edgecolors='black', linewidths=1.5, zorder=10)

        axes[0].set_title(f'平均异常收益(AAR)- {self.aar_df["N"].iloc[0]} 个事件',
                         fontsize=14, fontweight='bold')
        axes[0].set_xlabel('相对事件日的交易日 (t)')
        axes[0].set_ylabel('AAR')
        axes[0].legend(['事件日', '95% 置信区间', '显著 AAR (p<0.05)'], loc='best')
        axes[0].grid(True, alpha=0.3)

        # 图2: CAAR
        axes[1].plot(self.aar_df['event_time'], self.aar_df['CAAR'],
                    linewidth=2.5, color='darkblue', marker='o', markersize=5,
                    label='CAAR')

        # 95% 置信区间
        axes[1].fill_between(self.aar_df['event_time'],
                            self.aar_df['CAAR'] - 1.96 * self.aar_df['SE_CAAR'],
                            self.aar_df['CAAR'] + 1.96 * self.aar_df['SE_CAAR'],
                            alpha=0.3, color='blue', label='95% 置信区间')

        axes[1].axvline(x=0, color='red', linestyle='--', linewidth=2,
                       alpha=0.7, label='事件日')
        axes[1].axhline(y=0, color='black', linestyle='-', alpha=0.5, linewidth=1)

        # 标注最终 CAAR
        final_caar = self.aar_df['CAAR'].iloc[-1]
        axes[1].annotate(f'CAAR = {final_caar:.2%}',
                        xy=(self.aar_df['event_time'].iloc[-1], final_caar),
                        xytext=(10, 10), textcoords='offset points',
                        fontsize=12, fontweight='bold',
                        bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.5),
                        arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'))

        axes[1].set_title(f'平均累积异常收益(CAAR)- {self.aar_df["N"].iloc[0]} 个事件',
                         fontsize=14, fontweight='bold')
        axes[1].set_xlabel('相对事件日的交易日 (t)')
        axes[1].set_ylabel('CAAR')
        axes[1].legend(loc='best')
        axes[1].grid(True, alpha=0.3)

        plt.tight_layout()
        plt.savefig('multi_event_study_results.png', dpi=300, bbox_inches='tight')
        plt.show()

# 多事件示例
print("\n" + "="*70)
print("多事件研究示例:30个并购公告")
print("="*70)

# 模拟30个并购事件
n_events = 30
n_days_per_event = 300

events_data = []

for event_id in range(1, n_events + 1):
    # 生成市场收益
    market_ret = np.random.normal(0.0005, 0.015, n_days_per_event)

    # 生成股票收益(不同的 alpha 和 beta)
    alpha = np.random.normal(0.0003, 0.0001)
    beta = np.random.normal(1.2, 0.3)
    epsilon = np.random.normal(0, 0.01, n_days_per_event)
    stock_ret = alpha + beta * market_ret + epsilon

    # 在事件日添加随机异常收益(有正有负)
    event_day_idx = 250
    event_effect = np.random.normal(0.03, 0.02)  # 平均3%,标准差2%
    stock_ret[event_day_idx] += event_effect

    # 事件后1-3天也可能有效应
    stock_ret[event_day_idx + 1] += event_effect * 0.4
    stock_ret[event_day_idx + 2] += event_effect * 0.2

    # 生成日期
    dates = pd.date_range(f'2022-{event_id:02d}-01', periods=n_days_per_event, freq='B')

    # 创建事件时间
    event_time = np.arange(-event_day_idx, n_days_per_event - event_day_idx)

    # 保存数据
    for t in range(n_days_per_event):
        events_data.append({
            'event_id': f'Event_{event_id:02d}',
            'date': dates[t],
            'event_time': event_time[t],
            'stock_return': stock_ret[t],
            'market_return': market_ret[t]
        })

events_df = pd.DataFrame(events_data)

# 运行多事件研究
mes = MultiEventStudy(events_df,
                     estimation_window=(-250, -11),
                     event_window=(-10, 10))

mes.run_all_events()
aar_results = mes.calculate_AAR_CAAR()
mes.plot_AAR_CAAR()

实战案例:并购公告的市场反应

案例背景

研究问题:上市公司宣布并购时,股价如何反应?

假设

  • H1:并购公告日,目标公司股价上涨(收购溢价)
  • H2:收购公司股价可能下跌(支付过高)
python
# 案例:模拟真实的并购事件研究
print("\n" + "="*70)
print("案例:并购公告对股价的影响")
print("="*70)

# 模拟数据
np.random.seed(42)

# 并购公告日期
announcement_date = pd.Timestamp('2023-06-15')

# 生成交易日历(前后各200天)
dates = pd.bdate_range(start='2022-10-01', end='2024-01-31')

# 生成市场收益(沪深300指数)
market_returns = np.random.normal(0.0003, 0.012, len(dates))

# 目标公司收益(被收购公司)
# 市场模型参数
target_alpha = 0.0002
target_beta = 1.1
target_epsilon = np.random.normal(0, 0.015, len(dates))
target_returns = target_alpha + target_beta * market_returns + target_epsilon

# 在公告日添加显著正异常收益(+20%,典型的收购溢价)
announcement_idx = dates.get_loc(announcement_date)
target_returns[announcement_idx] += 0.20

# 公告前几天可能有内幕交易(信息泄露)
target_returns[announcement_idx - 1] += 0.03
target_returns[announcement_idx - 2] += 0.02

# 公告后几天继续上涨(市场消化信息)
target_returns[announcement_idx + 1] += 0.05
target_returns[announcement_idx + 2] += 0.02

# 转为 Series
target_ret_series = pd.Series(target_returns, index=dates, name='目标公司收益')
market_ret_series = pd.Series(market_returns, index=dates, name='市场收益')

print(f"\n案例设定:")
print(f"  目标公司: ABC 科技有限公司")
print(f"  收购公司: XYZ 集团")
print(f"  公告日期: {announcement_date.strftime('%Y年%m月%d日')}")
print(f"  收购溢价: 每股 50 元,较市价溢价 30%")
print(f"  交易金额: 100 亿元人民币")

# 运行事件研究
es_ma = EventStudy(
    stock_returns=target_ret_series,
    market_returns=market_ret_series,
    event_date=announcement_date,
    estimation_window=(-250, -11),
    event_window=(-20, 20)
)

# 完整分析流程
print("\n" + "="*70)
print("事件研究分析流程")
print("="*70)

alpha, beta = es_ma.estimate_market_model()
es_ma.calculate_abnormal_returns()
es_ma.calculate_CAR()
event_data, t_car, p_car = es_ma.test_significance(test_window=(-20, 20))

# 额外分析:不同窗口的 CAR
print("\n" + "="*70)
print("不同事件窗口的累积异常收益")
print("="*70)

for window_name, window in [
    ('公告前5天', (-5, -1)),
    ('公告日', (0, 0)),
    ('公告后5天', (1, 5)),
    ('公告日±1天', (-1, 1)),
    ('公告日±3天', (-3, 3)),
    ('公告日±10天', (-10, 10)),
    ('完整窗口', (-20, 20))
]:
    start, end = window
    window_data = es_ma.data[
        (es_ma.data['event_time'] >= start) &
        (es_ma.data['event_time'] <= end)
    ]

    car = window_data['AR'].sum()
    L = len(window_data)
    sigma_car = np.sqrt(es_ma.model.mse_resid) * np.sqrt(L)
    t_stat = car / sigma_car
    p_value = 2 * (1 - stats.norm.cdf(np.abs(t_stat)))

    sig_marker = '***' if p_value < 0.01 else ('**' if p_value < 0.05 else ('*' if p_value < 0.10 else ''))

    print(f"{window_name:15s} [{start:4d}, {end:4d}]: "
          f"CAR = {car:8.2%}  (t = {t_stat:6.3f}, p = {p_value:.4f}) {sig_marker}")

# 可视化
es_ma.plot_results()

# 结论
print("\n" + "="*70)
print("研究结论")
print("="*70)
print("""
1. 市场模型估计:
   - 目标公司的 β = 1.10,略高于市场平均,属于高风险股票
   - 估计窗口的 R² 约为 0.45,市场模型拟合良好

2. 并购公告的市场反应:
   - 公告日异常收益约 +20%,符合典型的收购溢价
   - 公告前2天出现异常收益(+5%),可能存在信息泄露
   - 公告后异常收益持续(+7%),市场逐步消化利好消息

3. 统计显著性:
   - CAR(-1, +1) = +28%,在 1% 水平显著 (t = 8.5, p < 0.001)
   - CAR(-10, +10) = +35%,在 1% 水平显著 (t = 6.2, p < 0.001)

4. 投资启示:
   - 并购公告对目标公司股价有显著正面影响
   - 市场对并购消息反应迅速,公告日当天大部分溢价已体现
   - 公告前的异常收益提示可能存在内幕交易,监管部门应关注
""")

本节小结

事件研究核心步骤

  1. 设计时间窗口

    • 估计窗口:
    • 事件窗口:
  2. 估计正常收益模型

    • 市场模型(推荐):
    • 均值调整模型:
  3. 计算异常收益

    • AR:
    • CAR:
  4. 统计检验

    • 单日 AR:
    • CAR:
    • 多事件:AAR 和 CAAR 的横截面检验
  5. 可视化与解读

    • AR 柱状图
    • CAR 累积图
    • 置信区间

实践要点

问题解决方案
如何选择估计窗口长度?通常 120-250 天,需要足够数据但避免结构性断点
事件窗口多长合适?短期事件(盈利公告):±3 天;长期事件(并购):±20 天
如果 β 不稳定怎么办?使用市场调整模型或 Fama-French 三因子模型
如何处理事件聚集?使用日历时间组合方法(calendar-time portfolio)
如何检验长期效应?使用 BHAR(Buy-and-Hold Abnormal Returns)

扩展方法

  1. Fama-French 三因子模型

  2. 条件事件研究

    • 按公司特征(规模、行业)分组
    • 按事件特征(交易金额、支付方式)分组
  3. 长期异常收益

    • BHAR(Buy-and-Hold Abnormal Returns)
    • 日历时间组合方法

下节预告

下一节中,我们将总结时间序列分析的全部内容,并提供10道高难度练习题。


延伸阅读

经典文献

  1. Fama, E. F., Fisher, L., Jensen, M. C., & Roll, R. (1969). "The Adjustment of Stock Prices to New Information." International Economic Review, 10(1), 1-21.

    • 事件研究方法的开山之作
  2. Brown, S. J., & Warner, J. B. (1985). "Using Daily Stock Returns: The Case of Event Studies." Journal of Financial Economics, 14(1), 3-31.

    • 事件研究方法论的系统总结
  3. MacKinlay, A. C. (1997). "Event Studies in Economics and Finance." Journal of Economic Literature, 35(1), 13-39.

    • 权威综述文章,必读
  4. Kothari, S. P., & Warner, J. B. (2007). "Econometrics of Event Studies." In Handbook of Corporate Finance: Empirical Corporate Finance, 3-36.

    • 最新的方法论综述

应用文献

  1. Andrade, G., Mitchell, M., & Stafford, E. (2001). "New Evidence and Perspectives on Mergers." Journal of Economic Perspectives, 15(2), 103-120.

    • 并购事件研究经典
  2. Kothari, S. P., & Warner, J. B. (1997). "Measuring Long-Horizon Security Price Performance." Journal of Financial Economics, 43(3), 301-339.

    • 长期异常收益测量

事件研究:揭示市场如何消化新信息!

基于 MIT 许可证发布。内容版权归作者所有。