2025年4月3日,美国宣布对中国进口商品征收高额关税,导致SPY指数当天下跌了4.8%,次日跌幅进一步达到6%。财经新闻惯用的标题便是“地缘政治不确定性扰乱市场”。

三个月前,也就是2024年8月5日,日元套利交易被迫平仓,导致SPY指数在单个交易日内下跌了3%,VIX指数更是飙升至65点。同样的标题再次出现:“地缘政治不确定性引发市场动荡”。

虽然这两起事件被贴上了相同的标签,但仔细分析数据会发现,它们实际上几乎没有共同点:在关税冲击下,黄金价格大幅上涨;而在日元套利交易平仓的过程中,黄金价格却下跌了;债券在日元套利交易中表现强劲,但在关税冲击下,债券与股票一样出现了抛售现象。

同样的标签,截然不同的市场反应。

为了解释这一现象,在这次分析中,我们将利用Python和EODHD的市场数据API,深入剖析这三起地缘政治事件。我们会追踪各种因素的变化顺序、期权市场在现货价格变动之前的预期反应,以及整个过程中新闻舆论所传达的信息。数据会告诉我们比新闻标题更详细的故事。

目录

先决条件

在开始学习之前,您需要掌握Python和pandas的基本知识。本文假设您能够读取DataFrame数据、操作字典、编写简单的函数,并理解基本的收益计算方法。

此外,您还需要准备以下工具:

  • Python 3.9或更高版本

  • EODHD的API密钥

  • 以下Python库:`requests`、`pandas`、`numpy`和`plotly`

  • 对SPY、QQQ、GLD、TLT和VIXY等ETF产品有基本了解

  • 理解收益、波动率、隐含波动率、期权价格偏斜、相关性以及市场情绪等概念

阅读这篇文章并不需要你成为期权交易方面的专家。文章中关于期权的分析其实基于一个简单的原理:当虚值看跌期权的价格相对于平价看涨期权而言变得更高时,说明市场正在为规避下行风险支付更多的成本。我们可以将这一现象作为判断市场恐慌情绪的依据,但并不能将其视为一个完整的期权定价模型。

我们的目标并不是构建一个完美的地缘政治风险模型,而是要展示不同的市场数据如何帮助我们区分各种类型的冲击。

准备工作:资产组合与数据来源

构建这个资产组合时,我们关注的是这样一个问题:哪些金融工具最能反映市场对某种冲击的反应方式?

广泛持有的股票(如SPY、QQQ、IWM)能够显示抛售行为的规模,以及哪些市值规模的板块受到的影响最为严重。行业ETFs(如XLE、XLF、ITA、XLK)则能体现这些经济冲击在市场中是如何被反映的。能源股、金融股、国防股和科技股对不同类型的冲击会有不同的反应。而避险资产(如GLD、TLT、UUP)则具有最高的诊断价值:黄金、债券和美元相对于股票的价格变动,能够告诉我们市场正在表达哪种恐惧情绪。VIXY指数直接反映了市场的隐含波动率。

综合这些11种金融工具的数据,我们就能为每一件事件生成一个“特征指纹”。

我们从EODHD的历史数据接口中获取了所需数据。对于每件事件,我们会选取事件发生前后各30天的数据进行分析。

import requests
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots

api_key = 'your_eodhd_api_key'

events = {
    'oct7_attack': {
        'date': '2023-10-07',
        'label': '哈马斯对以色列的袭击(2023年10月)',
        'shock_type': '信心冲击',
        'shock_label': '类型1 - 信心冲击'
    },
    'yen_carry_unwind': {
        'date': '2024-08-05',
        'label': '日元套利交易带来的影响 + 中东局势紧张(2024年8月)',
        'shock_type': '流动性冲击',
        'shock_label': '类型2 - 流动性冲击'
    },
    'tariff_shock': {
        'date': '2025-04-03',
        'label': '中美关税冲突带来的影响(2025年4月)',
        'shock_type': '结构性冲击',
        'shock_label': '类型3 - 结构性冲击'
    }
}

assets = {
    'spy': 'SPY.US', 'qqq': 'QQQ.US', 'iwm': 'IWM.US',
    'xle': 'XLE.US', 'xlf': 'XLF.US', 'ita': 'ITA.US',
    'xlk': 'XLK.US', 'gld': 'GLD.US', 'tlt': 'TLT.US',
    'uup': 'UUP.US', 'vixy': 'VIXY.US'
}

def fetch_prices(ticker, start, end):
    url = f'https://eodhd.com/api/eod/{ticker}'
    params = {
        'from': start,
        'to': end,
        'api_token': api_key,
        'fmt': 'json'
    }
    r = requests.get(url, params=params)
    df = pd.DataFrame(r.json())
    df['date'] = pd.to_datetime(df['date'])
    df = df.set_index('date')[['adjusted_close']]
    df.columns = [ticker.split('.')[0].lower()]
    return df

def fetch_event_prices(event_date, lookback=30, lookahead=30):
    start = (pd Timestamp(event_date) - pd.Timedelta(days=lookback)).strftime('%Y-%m-%d')
    end = (pdTimestamp(event_date) + pd.Timedelta(days=lookahead)).strftime('%Y-%m-%d')
    frames = [fetch_prices(ticker, start, end) for ticker in assets.values()]
    return pd.concat(frames, axis=1)

event_prices = {name: fetch_event_prices(e['date']) for name, e in events.items()}

event_prices.keys()

这样我们就得到了三个数据框:每个事件对应一个数据框,这些数据框都包含11列和大约60行数据,能够覆盖整个观察窗口期。

dict_keys(['oct7_attack', 'yen_carry_unwind', 'tariff_shock'])

所有的价格都已经进行了调整,因此任何分割或股息分配带来的影响都会被妥善处理。

重新定价序列引擎

在分别分析每个事件之前,我们需要一种统一的方法来衡量所有这些事件中发生的变化。重新定价序列引擎主要完成三项工作:首先将所有资产在事件发生当天的价格标准化为100,以便进行跨资产比较;其次会选取事件发生前后的一段特定时间窗口;最后会根据资产在T+1日的价格变动幅度对它们进行排序,从而确定哪些资产的价格变化最快。

def normalize_to_event(df, event_date):
    event_date = pdTimestamp(event_date)
    valid_dates = df.index[df.index >= event_date]
    anchor = valid_dates[0]
    normalized = df.div(df.loc[anchor]) * 100
    return normalized, anchor

def get_event_window(df, anchor, t_minus=5, t_plus=10):
    start_idx = df.index.get_loc(anchor) - t_minus
    end_idx = df.index.get_loc-anchor) + t_plus
    start_idx = max(start_idx, 0)
    return df.iloc[start_idx:end_idx + 1]

def repricing_leaderboard(window_df, anchor):
    anchor_idx = window_df.index.get_loc.anchor)
    post_event = window_df.iloc[anchor_idx:]
    cumulative_returns = (post_event / post_event.iloc[0] - 1) * 100
    t1_moves = cumulative_returns.iloc[1].abs().sort_values(ascending=False)
    return cumulative_returns, t1moves

event_windows = {}
leaderboards = {}

for name, meta in events.items():
    df = event_prices[name]
    normalized, anchor = normalize_to_event(df, meta['date'])
    window = get_event_window(normalized, anchor)
    cumret, t1_rank = repricing_leaderboard(window, anchor)
    event_windows[name] = {'window': window, 'anchor': anchor, 'cumret': cumret}
    leaderboards[name] = t1_rank
    print(f"\n{meta['label']}")
    print(f'基准日期:{anchor.date()}')
    print('T+1日价格变动排名:')
    print(t1_rank.round(2))

输出结果:

“哈马斯对以色列的袭击”(2023年10月)
基准日期:2023-10-09
T+1日价格变动排名:
vixy    3.35
iwm     1.13
xlf     0.73
ita     0.72
qqq     0.55
spy     0.52
uup     0.24
gld     0.17
xlk     0.15
tlt     0.14
xle     0.12
名称:2023-10-10 00:00:00,数据类型:float64

“日元套利操作与中东局势紧张”(2024年8月)
基准日期:2024-08-05
T+1日价格变动排名:
vixy    20.52
tlt      2.24
xlf      1.62
xlk      1.36
iwm      1.09
qqq      0.96
spy      0.92
gld      0.80
xle      0.61
ita      0.57
uup      0.32
名称:2024-08-06 00:00:00,数据类型:float64

“中美关税冲突”(2025年4月)
基准日期:2025-04-03
T+1日价格变动排名:
vixy    19.97
xle      9.20
ita      8.44
xlf      7.32
xlk      6.59
qqq      6.21
spy      5.85
iwm      4.46
gld      2.34
uup      1.11
tlt      1.09
名称:2025-04-04 00:00:00,数据类型:float64

VIXY在T+1时段领先于其他三个指标,这是合乎情理的——波动率的变化速度确实比其他任何因素都要快。但如果你忽略VIXY,就会发现这些指标的排名差异会变得非常明显。

在哈马斯发动袭击期间,各个资产类别的波动幅度都很小。其中,非VIXY指标中波动最大的是IWM,其波动率为1.13%。在日元套利交易被平仓的过程中,TLT的波动幅度排在第二位,为2.24%;由于债券被视为避险工具,因此其价格也出现了大幅上涨。而在关税冲击事件中,所有股票板块的涨幅都在4%到9%之间,而TLT的涨幅仅为1.09%,黄金的涨幅则为2.34%。

这三起事件导致了完全不同的价格定价机制。仅通过T+1时段的排名情况,就能清楚地了解各个市场实际上是如何对各种风险进行定价的。

关于10月7日的这个时间点:袭击发生在周六,而第一个交易日是10月9日,因此参考时间点是10月9日而非10月7日。这一点在后续的分析中非常重要。

期权数据与隐含波动率偏度

价格数据可以告诉我们发生了什么,而期权数据则能反映市场为规避这些风险愿意支付多少代价。

我们在这里计算的偏度指标非常直观:它表示虚值看跌期权的平均隐含波动率与平价看涨期权的平均隐含波动率之间的差异。当这个数值上升时,说明市场为防范下行风险而支付的成本相对较高,这种情绪其实就是恐惧的量化体现。
我们从EODHD的期权数据终端获取SPY期权的相关数据,并针对每个事件窗口对全部数据集进行筛选和处理。

def fetch_options_all(ticker, start, end, exp_cap):
    url = 'https://eodhd.com/api/mp/unicornbay/options/eod'
    all_records = []
    offset = 0
    limit = 1000
    cols = None

    while True:
        params = {
            'filter[underlying_symbol]': ticker,
            'filter[tradetime_from]': start,
            'filter[tradetime_to]': end,
            'filter[exp_date_to]': exp_cap,
            'fields[options-eod]': 'type,exp_date,strike,volatility,tradetime',
            'page[limit]': limit,
            'page[offset]': offset,
            'api_token': api_key,
            'compact': 1
        }
        r = requests.get(url, params=params)
        payload = r.json()

        if 'meta' not in payload:
            print(f'unexpected response at offset {offset}: {list(payload.keys())}')
            break

        if cols is None:
            cols = [f.strip() for f in payload['meta']['fields']]

        batch = payload['data']
        all_records.extend(batch)

        total = payload['meta']['total']
        offset += limit
        if offset >= total or not batch:
            break

    df = pd.DataFrame(all_records, columns=cols)
    df['tradetime'] = pd.to_datetime(df['tradetime'])
    df['exp_date'] = pd.to_datetime(df['exp_date'])
    df['strike'] = pd.to_numeric(df['strike'], errors='coerce')
    df['volatility'] = pd.to_numeric(df['volatility'], errors='coerce')
    return df.dropna(subset=['volatility', 'strike']).query('volatility > 0')

def compute_skew(df, spot):
    df = df.copy()
    df['moneyness'] = df['strike'] / spot

    for expiry in sorted(df['exp_date'].unique()):
        sub = df[df['exp_date'] == expiry]
        otmputs = sub[(sub['type'] == 'put') & (sub['moneyness'].between(0.90, 0.97))]
        atm_calls = sub[(sub['type'] == 'call') & (sub['moneyness'].between(0.97, 1.03))]
        if otmputs.empty or atm_calls.empty:
            continue

        daily_skew = []
        for date, puts in otm_puts.groupby('tradetime'):
            calls = atm_calls[atm_calls['tradetime'] == date]
            if calls.empty:
                continue
            skew = puts['volatility'].mean() - calls['volatility'].mean()
            daily_skew.append({'date': date, 'skew': skew})

        if daily_skew:
            print(f'  using expiry: {expiry.date()}, {len(daily_skew)} days')
            return pd.DataFrame(daily_skew).set_index('date').sort_index()

    return pd.DataFrame()

spy_skew = {}

for name, meta in events.items():
    anchor = event_windows[name]['anchor']
    spot = event_prices[name].loc[anchor, 'spy']
    start = (anchor - pd.Timedelta(days=20)).strftime('%Y-%m-%d')
    end = (anchor + pd.Timedelta(days=5)).strftime('%Y-%m-%d')
    exp_cap = (pd Timestamp(end) + pd.Timedelta(days=90)).strftime('%Y-%m-%d')
    raw = fetch_options_all('SPY', start, end, exp_cap)
    print(f'\n{meta["label"]} | total rows: {len(raw)}')
    skew_df = compute_skew(raw, spot)
    spy_skew[name] = skew_df
    print(skew_df)

输出结果:

哈马斯对以色列的袭击(2023年10月)| 总行数:10435
使用有效期至:2023-11-17,共3天
数据偏度分布:
日期                偏度值
2023-10-11      0.014164
2023-10-12      0.034279
2023-10-13      0.054055
在偏移量11000处出现异常响应:['errors']

日元走势逆转与中东局势紧张(2024年8月)| 总行数:10660
使用有效期至:2024-10-18,共11天
数据偏度分布:
日期                偏度值
2024-07-26      0.040748
2024-07-29      0.041219
2024-07-30      0.087402
2024-07-31      0.029824
2024-08-01      0.065074
2024-08-02      0.053369
2024-08-05      0.049848
2024-08-06      0.055957
2024-08-07      0.050664
2024-08-08      0.050283
2024-08-09      0.055462
在偏移量11000处出现异常响应:['errors']

中美关税冲突的影响(2025年4月)| 总行数:10698
使用有效期至:2025-06-20,共18天
数据偏度分布:
日期                偏度值
2025-03-14      0.042500
2025-03-17      0.029671
2025-03-18      0.027886
2025-03-19      0.029360
2025-03-20      0.026691
2025-03-21      0.008500
2025-03-24      0.013388
2025-03-25      0.022157
2025-03-26      0.012829
2025-03-27      0.009171
2025-03-28      0.026971
2025-03-31      0.036586
2025-04-01      0.022857
2025-04-02     -0.023000
2025-04-03      0.019729
2025-04-04      0.036729
2025-04-07      0.005257
2025-04-08      0.041543

在开始分析这些事件之前,有几点需要注意。10月7日那组数据只有三个数据点,而且全部都是事件发生之后的数据,这是因为在那个时期,可用的期权数据覆盖范围有限。而中美关税冲突那组数据在事件发生前的数据覆盖范围最为全面,最早的可追溯到3月14日,也就是事件发生前近三周的时间。此外,在4月2日,也就是危机发生的前一天,该组数据的偏度值呈现负值。当我们具体分析这些事件时,会进一步了解这些数据所代表的含义。

综合压力指数

仅使用偏度信号来评估市场压力存在一定的局限性:因为某些与地缘政治紧张局势无关的因素也可能会导致偏度值突然上升。为了使这一指标更加可靠,我们将其与另一个信号结合起来进行分析:即SPY指数与GLD黄金期货在过去10天内的相关性。

在正常情况下,股票和黄金的价格走势通常是弱相关或负相关的。然而当市场压力加剧时,这种关联关系就会被打破。通过监测这种关系的变化,我们可以获得另一个独立的市场压力指标,而这个指标并不依赖于期权价格的变化。

在将这两个信号结合起来进行分析之前,都会先对它们进行z分数转换,这样就可以避免由于数据规模差异而导致其中一个指标占据主导地位。由于相关性下降实际上意味着市场压力正在增加,因此在对相关性信号进行z分数转换时,会将其数值取反。最终得到的综合指数就是这两个信号的算术平均值。

def build_composite(event_name, skew_df, event_prices_df, anchor):
    prices = event_prices_df[['spy', 'gld]].copy()
    prices['corr'] = prices['spy'].rolling(10).corr(prices['gld'])

    def zscore(s):
        return (s - s.mean()) / s.std()

    skew_z = zscore(skew_df['skew'])
    corr_z = zscore(prices['corr'].dropna())

    corr_z = corr_z * -1

    combined = pd.concat([skew_z.rename('skew_z'), corr_zrename('corr_z')], axis=1).dropna()
    combined['composite'] = combined.mean(axis=1)

    combined['stress_flag'] = combined['composite'] > 1.0

    return combined

composites = {}

for name, meta in events.items():
    anchor = event_windows[name]['anchor']
    skew_df = spy_skew[name]
    prices_df = event_prices[name]
    comp = build_composite(name, skew_df, prices_df, anchor)
    composites[name] = comp
    print(f"\n{meta['label']}")
    print(comp.round(3))

输出结果:

哈马斯对以色列的袭击(2023年10月)
            skew_z  corr_z  composite  stress_flag
日期                                              
2023-10-11  -1.003  -1.186     -1.094        False
2023-10-12   0.006  -1.316     -0.655        False
2023-10-13   0.997  -0.971      0.013        False

日元走势逆转与中东局势紧张(2024年8月)
            skew_z  corr_z  composite  stress_flag
日期                                              
2024-07-26  -0.808  -0.863     -0.835        False
2024-07-29  -0.776  -1.074     -0.925        False
2024-07-30   2.343  -0.559      0.892        False
2024-07-31  -1.546  -0.082     -0.814        False
2024-08-01   0.835   0.933      0.884        False
2024-08-02   0.044   2.117      1.081         True
2024-08-05  -0.194   1.977      0.892        False
2024-08-06   0.219   1.525      0.872        False
2024-08-07  -0.138   1.170      0.516        False
2024-08-08  -0.164   0.881      0.358        False
2024-08-09   0.186   0.371      0.278        False

中美关税冲突(2025年4月)
            skew_z  corr_z  composite  stress_flag
日期                                              
2025-03-17   0.511   0.516      0.513        False
2025-03-18   0.398   0.493      0.445        False
2025-03-19   0.491   0.154      0.323        False
2025-03-20   0.322  -0.209      0.057        False
2025-03-21  -0.830  -1.023     -0.926        False
2025-03-24  -0.520  -0.999     -0.759        False
2025-03-25   0.035  -0.777     -0.371        False
2025-03-26  -0.556  -0.566     -0.561        False
2025-03-27  -0.787   0.096     -0.346        False
2025-03-28   0.340   1.093      0.716        False
2025-03-31   0.949   1.179      1.064         True
2025-04-01   0.080   1.309      0.694        False
2025-04-02  -2.824   1.190     -0.817        False
2025-04-03  -0.119   1.047      0.464        False
2025-04-04   0.958   0.119      0.539        False
2025-04-07  -1.035  -0.794     -0.915        False
2025-04-08   1.263  -1.274     -0.006        False

“stress_flag”阈值的设定为1.0。在所有三个事件中,都有两天被标记为高风险阶段:2024年8月2日是日元走势逆转的事件,2025年3月31日则是中美关税冲突事件。这两天都发生在相关事件发生之前。而2025年10月7日的数据量太少,因此无法生成有意义的综合分析结果。

在关税冲突事件中,2025年4月2日的数据值得注意:当时的skew_z值为-2.824,这是整个数据集中负偏度值最大的记录。尽管相关系数仍然处于较高水平,但这一数值还是导致综合指标出现了负值。在2025年SPY指数单日跌幅最大之前的那天,期权市场实际上预期后市上涨的概率要高于下跌的概率。这个现象不容忽视,我们稍后会再次讨论它。

新闻情绪分析

最后一个数据层是新闻情绪分析结果。EODHD的情绪分析API会根据财经新闻报道,为每个股票代码生成一个每日标准化得分,该得分的范围是从-1(强烈负面情绪)到+1(强烈正面情绪)。我们选取SPY指数的情绪分析结果作为整个市场的代表指标,并使用与期权分析中相同的时间窗口来进行数据分析。

def fetch_sentiment(ticker, start, end):
url = 'https://eodhd.com/api/sentiments'
params = {
's': ticker,
'from': start,
'to': end,
'api_token': api_key,
'fmt': 'json'
}
r = requests.get(url, params=params)
data = r.json()
key = ticker if ticker in data else ticker + '.US'
if key not in data:
return pd.DataFrame()
df = pd.DataFrame(data[key])
df['date'] = pd.to_datetime(df['date'])
df = df.set_index('date')[['normalized]].rename(columns={'normalized': 'sentiment'))
return df.sort_index()

event_sentiment = {}

for name, meta in events.items():
anchor = event_windows[name]['anchor']
start = (anchor - pd.Timedelta(days=20)).strftime('%Y-%m-%d')
end = (anchor + pd.Timedelta(days=10)).strftime('%Y-%m-%d')
sent_df = fetch_sentiment('SPY', start, end)
event_sentiment[name] = sent_df
print(f"\n{meta['label']}")
print(sent_df)

输出结果:
哈马斯对以色列的袭击(2023年10月)
情感指数
日期
2023-09-25 0.997
2023-09-26 0.986

日元套利交易带来的影响及中东局势紧张(2024年8月)
情感指数
日期
2024-07-17 0.9340
2024-07-22 0.9460
2024-07-23 0.9550
2024-07-25 0.9925
2024-07-26 0.9860
2024-07-29 0.9850
2024-07-30 0.9630
2024-07-31 0.9950
2024-08-02 0.3350
2024-08-05 0.9780
2024-08-06 0.3603
2024-08-15 0.9980

中美关税冲突的影响(2025年4月)
情感指数
日期
2025-03-14 -0.9890
2025-03-15 0.9930
2025-03-17 -0.7010
2025-03-18 0.9990
2025-03-20 -0.8900
2025-03-22 0.9950
2025-03-24 0.9600
2025-03-27 0.9830
2025-03-28 0.9917
2025-04-03 0.9365
2025-04-05 0.0130
2025-04-06 0.9990
2025-04-07 0.9870
2025-04-09 0.5460
2025-04-10 0.8079
2025-04-11 0.0929
2025-04-12 -0.9920
2025-04-13 0.0130

有两点值得注意:对于日元套利交易带来的影响,从7月17日到7月31日,情感指数一直在0.934到0.995之间波动;而到了7月30日,这种情绪的波动幅度已经明显加大,整体趋势也在持续上升。然而,这一指数并没有反映出期权市场所预期的那种紧张情绪。至于中美关税冲突的影响,在4月3日SPY指数下跌了4.8%的那个交易日,情感指数仍然为0.9365,属于积极偏正的情绪。显然,新闻界当时并不了解接下来会发生什么。

关于10月7日的情绪数据,只有9月底的两个数据点,且这两个数值都接近0.9。这些数据距离相关事件发生已经过去了近两周,因此它们并不能为我们提供关于这次袭击发生时市场情绪的任何信息。由于相关数据的覆盖范围太有限,这些数据对进行情绪分析来说帮助不大。

事件1:2023年10月7日哈马斯对以色列的袭击

2023年10月7日哈马斯对以色列的袭击是一场重大的地缘政治冲击,但市场的反应却并不强烈。

事件1的价格重估序列图(作者提供)

截至10月9日,SPY指数相比10月6日的收盘价上涨了0.64%。这一变化的发生时间点是10月9日周一,因为袭击事件发生在周六。GLD和TLT这两种资产都出现了上涨趋势;而VIX指数的涨幅在T+1交易日达到了3.35%,这一幅度与其他两次事件中20%的涨幅相比显得相对较小。在两周内,大多数资产的价格都回到了事件发生前的水平。

市场对于这一事件的解读非常明确:这是一场区域性的冲突,其直接对经济造成的影响较为有限。以色列既不是主要的石油供应国,也不是重要的贸易伙伴,同时它在全球供应链中的地位也不足以导致人们对其盈利能力的预期发生重大变化。因此,这种不确定性确实是存在的,但它的经济后果并不明显。

这一区别在资产市场的反应中表现得非常清晰:GLD和TLT这两种债券价格上涨,而UUP指数则保持平稳;股票价格基本上没有发生变化。当黄金和债券的价格同时上涨,而股票价格却停滞不前时,市场实际上是在表现出典型的“避险行为”——资金纷纷流入这些防御性资产,以此来应对不确定性,而不是因为基本面的变化导致了价格的重新调整。

对于这次事件而言,我们可以获得的偏度数据仅限于事件发生后的三天:10月11日、12日和13日。在这段时间内,偏度数值从0.014稳步上升到了0.054,这一变化与市场在袭击事件发生后仍持续存在不确定性的预期是一致的。

但由于袭击事件发生在周末,而且EODHD机构在这一时期提供的期权相关数据较为有限,因此我们无法获得事件发生前的偏度数据。所以我们无法确定期权市场是否提前预料到了这次事件的发生。

关于这次事件的综合指标数据同样较为稀少,只有三个数据点,且没有任何异常现象出现。由于数据量不足,我们无法根据这些数据得出任何关于早期预警信号的结论。

从分析的角度来看,这是三个案例中最缺乏信息量的一个。尽管如此,我们仍然将其纳入分析范围,因为这一事件所表现出的价格变化特征具有一定的参考价值,而且它与其他两次事件的差异也非常明显。小幅的价格波动、清晰的避险行为模式,以及市场迅速恢复的正常走势,这些都说明这次事件属于某种特定类型:在这种类型的事件中,市场的恐慌情绪并不会导致经济成本的增加。即使期权数据无法提供更多信息,这一分类仍然具有重要的意义。

事件2:2024年8月5日日元套利交易带来的风险释放

2024年8月的这次事件在三个案例中分析价值最高。同时,数据也最清楚地证明了,在市场崩盘之前,结构化的市场信号就已经反映了人们对于未来风险的担忧。

通过这张图表,我们可以清晰地看到这次事件中价格变化的具体过程。

价格变动的顺序本身就讲述了一个清晰的故事:VIX指数在T+1交易日暴涨了20.52%;TLT指数的波动幅度位居第二,为2.24%,由于被视为避险资产,其交易量也大幅增加;而整个股票市场都出现了抛售潮。

这就是流动性冲击带来的后果。日本银行在7月31日意外提高了利率,这一举措引发了大规模的日元套利交易平仓行为。

导致这种抛售行为的根本原因并非经济基本面的变化,而是交易者的持仓状况。那些之前以低廉成本借入日元来购买高收益资产的交易者,现在不得不同时抛售这些资产以平仓自己的头寸。由于所有资产都是出于同样的原因被抛售,因此它们之间的相关性也消失了。

现在让我们看看在这一切发生之前,相关数据的变化情况:

事件2:波动率偏度图表

在危机发生前的6天,也就是7月30日,波动率偏度骤升到了0.087,这一数值远高于事件发生前整个时期的任何其他数据。7月31日这一数值有所下降,但在8月1日和2日又再次上升。最终危机发生在8月5日。

7月30日的这一波动率偏度峰值是本次分析中最重要的数据点。引发套利交易平仓行为的日本银行利率决策是在7月31日做出的。而在事件发生前一天,期权市场就已经预期到了价格下行风险会加剧,而不是在事件发生后才出现这种预期。显然,在消息公开之前,就有人——或者更有可能是很多人——开始购买SPY期权的看跌保险。

现在让我们再看看同一时期市场情绪的变化情况:

事件2:市场情绪与波动率偏度图表

从7月17日到7月31日,市场情绪一直处于0.934到0.995之间,每天都处于近乎极度看涨的状态。而在7月30日,也就是波动率偏度骤升的同一天,市场情绪仍然为0.963。这说明市场并未对这一变化感到担忧,但期权市场却已经产生了警觉。

直到8月2日,市场情绪才降至0.335,这距离波动率偏度峰值出现已有三天,也距离危机发生还有三天。事实上,在此之前的近一周时间里,期权市场就已经在不断发出警示信号了。

综合来看,8月2日被认定为一个压力较大的日子,这一判断主要是基于波动率偏度指标的变化。自7月底以来,SPY指数与黄金价格之间的相关性一直在下降,因为黄金的价格开始与股票市场脱节。虽然7月30日的波动率偏度峰值并没有被及时捕捉到,但由于随后这一指标有所回落,综合评估结果仍然显示出了风险信号。然而,7月30日波动率偏度的急剧上升与8月2日综合指标所显示的风险信号相结合,为8月5日的危机发生提供了两阶段的预警信号。

在这项分析中,日元相关的市场走势最为清楚地证明了这样一个论点:结构化的市场信号所蕴含的信息是新闻报道所无法传达的。期权市场并没有具备预见能力,但它确实反映出了某些新闻标题所没有提及的因素。

事件3:2025年4月的美中关税冲突

2025年4月的关税冲突是这项分析中最值得关注的事件——并非因为这些市场信号起到了预期的作用,而是因为它们在哪些方面出现了失误。

事件3相关价格变动图表

这些数据非常惊人:SPY指数在T+1交易日下跌了5.85%,并且在之后的几天里持续走低;所有股票板块的跌幅都在4%到9%之间,其中能源和贸易相关行业的跌幅最大,达到了9.20%;ITA指数的跌幅也为8.44%;科技股则下跌了6.59%。

这些其实并不是市场波动带来的结果,而是市场对在新的贸易环境下这些公司的实际价值进行了重新评估后产生的价格变化。

在这张图表中,黄金作为“避险资产”的表现最为明显:GLD指数在T+1交易日上涨了2.34%,在随后的几天里继续攀升;而TLT指数的涨幅仅为1.09%,之后便开始下跌。债券和股票市场同步走低,没有出现投资者纷纷转向债券的现象——黄金才是真正的避险资产。

正是这一点将结构性冲击与其他两种类型的事件区分开来:在信心受到冲击的情况下,黄金和债券都会上涨;在流动性出现问题的情况下,债券的价格会大幅上涨;但在结构性冲击中,债券并不能提供任何保护,因为这种冲击本身就会引发人们对财政政策和货币政策前景的质疑,而黄金则是唯一没有交易对手方的资产。

在这里,分析变得有些棘手了……

2025年4月2日,在市场暴跌前一天,期权市场的价格偏斜度为-0.023,这意味着处于执行价附近的看涨期权价格高于处于虚值区域的看跌期权价格。期权市场并没有将下行风险纳入考虑范围,反而预期股价会上涨。

到了3月中旬,这种价格偏斜度还一直在上升,范围在0.025到0.042之间;但到了3月底,这一数值开始逐渐下降。当关税政策正式宣布时,期权市场已经主动降低了自身所持有的看涨头寸所带来的风险。

对此有两种合理的解释:第一种是,在整个3月份,市场都将关税风险视为一种谈判手段,直到4月初才认为双方很可能会达成协议;因此4月2日时价格偏斜度为负值,反映了人们普遍认为宣布的关税政策不会全面实施。第二种可能是,期权市场根本没有获得相关的信息——因为4月2日宣布的关税措施比大多数参与者预期的要严重得多、也来得更突然。

无论如何,期权市场在这里并没有起到预警作用。这并不是方法论上的缺陷,而只是一个事实。偏度指标反映的是市场参与者愿意为获得风险保护支付多少费用;但如果所有参与者都认为某种风险不值得被纳入定价范围,那么偏度指标就无法发出警报——而这样的判断也可能是错误的。

事件3综合压力评分图表

该综合指标在3月31日标出了这一天为“压力日”——而这距离市场崩盘还有三天时间。这一信号完全来自于“相关性下降”这一因素,而非偏度指标。事实上,随着黄金价格上涨而股票走势疲软,SPY与GLD之间的滚动相关性在整个3月底都在持续恶化;即使偏度指标的值在减小,该综合指标仍然能够捕捉到这种脱节现象。

到了4月2日,这一综合指标的值骤降至-0.817。此时偏度指标已经变成了负值,且其影响力远远超过了仍然处于较高水平的“相关性下降”信号,使得综合指标的值跌到了零以下。实际上,在2025年SPY单日跌幅最大的那天之前,这个综合指标却显示“没有压力存在”。

关税冲击暴露了任何基于期权定价构建的预警系统所存在的根本局限性:当整个市场都错误地评估了某种风险时,这些预警信号自然也会反映出这种错误的判断。虽然“相关性下降”这一因素在这里表现得更好一些,但仅仅依靠两个指标中的一个来做出判断,仍然是不可靠的。

综合分析:热力图

对各个单独事件的分析得出了三种不同的结论;而热力图将这些结果并列展示出来,使得它们之间的差异一目了然。

fig = make_subplots(rows=1, cols=3,
    subplot_titles=[e['label'] for e in events.values ],
    horizontal_spacing=0.08)

for i, (name, meta) in enumerate(events.items()):
    window = event_windows[name]['window']
    anchor = event.windows[name]['anchor']
    anchor_idx = window.index.get_loc(anchor)

    start_i = max-anchor_idx - 3, 0)
    end_i = min.anchor_idx + 8, len(window))
    slice_df = window.iloc[start_i:end_i].copy()
    slice_df.columns = [c.upper() for c in slice_df.columns]

    anchor_pos = anchor_idx - start_i
    anchor_vals = slice_df.iloc[anchor_pos]
    pct_df = ((slice_df - anchor_vals) / anchor_vals * 100).round(2)

    n_days = len(pct_df)
    t_labels = [f'T{d:+d}' for d in range(-anchor_pos, -anchor_pos + n_days)]

    fig.add_trace(go.Heatmap(
        z=pct_df.values.T,
        x=t_labels,
        y=list(pct_df.columns),
        colorscale='RdYlGn',
        zmid=0,
        zmin=-15,
        zmax=15,
        showscale=(i == 2),
        colorbar=dict(title='% return from T0')
    ), row=1, col=i+1)

fig.update_layout(
    title='资产回报热力图——事件发生前3天至后7天的变化',
    template='plotly_dark',
    height=500
)

for annotation in fig['layout']['annotations']:
    annotation['font'] = dict(size=11)
    annotation['y'] = 1.02

fig.show()

资产收益热力图

共有三个面板,每个面板对应一个事件,分别显示从T-3日到T+7日期间,各类资产相对于事件发生当日的收益百分比。绿色表示资产价值相对上升,红色则表示资产价值下降。颜色刻度的范围为正负15%,因此关税冲击带来的极端价格波动并不会掩盖10月7日那些较小的价格变动。

对于VIXY而言,不同事件会导致截然不同的表现。在哈马斯袭击事件和关税冲击事件中,由于市场波动性急剧上升,VIXY在事件发生后的数值会呈现绿色;而在日元套利交易被平仓的过程中,VIXY的数值则一直为红色——这并非因为波动性没有增加,而是因为在8月5日这一关键日期时,VIXY已经处于历史最高水平,因此相对于事件发生当日的变化来看,其数值显得持平甚至为负。

看看GLD的表现吧。在哈马斯袭击事件中,它的数值基本保持中性,仅表现出轻微的避险效应;在日元套利交易被平仓的过程中,由于抛售压力得到缓解,黄金价格回升,GLD的数值也转为绿色;而在关税冲击事件中,GLD的数值则呈现深绿色,并且这种趋势持续了整个时间段,这是在这三次事件中所有资产中表现最为强劲、持续时间最长的。

TLT的表现展现了最鲜明的对比:在哈马斯袭击事件中,它的数值接近中性;在日元套利交易被平仓的过程中,由于债券价格大幅上涨,其数值转为绿色;而在关税冲击事件中,从事件发生当日到第三天,其数值一直为深红色——这种统一的下跌趋势说明市场当时是在重新评估各种基本因素,而不仅仅是恐惧情绪在起作用。

从数据的角度来看,这三类事件其实有着截然不同的特征。同样的标签被用来描述它们,但实际数据却揭示了它们之间迥异的差异。

最后的思考

在这次分析中,这三类事件都被贴上了相同的标签。但实际上,数据却显示出了它们之间的区别。

“信心冲击”会反映市场的恐惧情绪,但并不会体现经济损害的程度;黄金和债券的价格会上涨,股票则会保持稳定,而且恢复速度往往比人们想象的要快。

“流动性冲击”是一种机械性的反应:所有资产都会被抛售,因为投资者需要调整自己的持仓结构,而不是因为基本因素发生了变化。

“结构性冲击”则会导致市场重新评估企业在不同经济环境下的实际价值;债券在这种情况下无法提供任何保护作用,黄金才是唯一的可靠避险工具,而市场的恢复时间则难以预测。

利用EODHD的历史数据和期权数据构建的IV偏度与相关性指标,在其中一种事件中能够有效反映市场情况,在另一种事件中只能部分发挥作用,而在第三种事件中则完全失效了。但这并不意味着这些指标毫无价值,而是提醒我们需要注意它们所衡量的是什么。偏度的变化实际上反映了投资者为规避下行风险而愿意支付的成本;当整个市场都认为某种风险不值得被过度关注时,偏度就会趋于平稳——但这种平静并不代表安全已经来临。

这个框架所提供的最有用的信息并不是一种信号,而是一个问题:这种冲击属于哪一类?这个问题的答案会决定后续的一切发展方向。

Comments are closed.