5D Library › 信贷周期、自组织临界性与市场相变的统计物理 · English
第六章 预警信号的争议:临界慢化与对数周期幂律
前五章讨论的都是机制:债务怎样累积,雪崩怎样传递,抛售怎样互相放大,波动怎样成群出现。一个自然的问题是:既然这些系统在突变之前有可以描述的结构,能不能在突变之前识别它?本章讨论两种常被提到的方法,并用模拟数据检验它们能做到什么、做不到什么。
临界慢化
许多动力系统在接近一个「临界转变」(例如分岔点)时,有一个共同的特征:受到小扰动后恢复得越来越慢。这一现象称为临界慢化;谢弗等人系统地阐述了它作为预警信号的用法:如果恢复变慢,时间序列的一阶自相关和方差就会上升 (Scheffer et al. 2009)。达科斯等人在古气候记录中发现,几次突然的气候转变之前,自相关确实有上升的趋势 (Dakos et al. 2008)。
推导
用最简单的一阶自回归序列检验:「接近」组的自回归系数在 1,000 步中从 0.3 逐渐升到 0.95,模拟一个慢慢失去稳定性的系统;「对照」组的系数始终是 0.3,什么都没有变化。在 250 步的移动窗口中计算一阶自相关,用肯德尔 衡量它的上升趋势, 就算「发出预警」。每组 200 条序列。
# Critical slowing down (Scheffer et al. 2009): as a system nears a tipping point it recovers more
# slowly from small shocks, so lag-1 autocorrelation and variance rise. Test it on AR(1) series:
# x[t+1] = phi[t] x[t] + noise
# "approaching": phi rises from 0.3 to 0.95 over 1,000 steps (a system drifting towards instability)
# "control": phi stays at 0.3 (nothing is changing)
# Indicator: lag-1 autocorrelation in a moving window of 250 steps; its trend is measured by
# Kendall's tau. A "warning" is declared when tau > 0.5. 200 series of each kind.
import random, statistics as st
def series(n, ramp, rnd):
x, out = 0.0, []
for t in range(n):
phi = 0.3 + (0.65 * t / n if ramp else 0.0)
x = phi * x + rnd.gauss(0, 1); out.append(x)
return out
def ac1(w):
m = st.fmean(w)
return sum((w[i] - m) * (w[i + 1] - m) for i in range(len(w) - 1)) / sum((u - m) ** 2 for u in w)
def kendall(y):
c = d = 0
for i in range(len(y)):
for j in range(i + 1, len(y)):
if y[j] > y[i]: c += 1
elif y[j] < y[i]: d += 1
return (c - d) / (c + d)
def tau_of(x, win=250, step=10):
return kendall([ac1(x[i - win:i]) for i in range(win, len(x) + 1, step)])
rnd = random.Random(8)
for ramp, lab in ((True, "approaching"), (False, "control")):
taus = [tau_of(series(1000, ramp, rnd)) for _ in range(200)]
print(f"{lab:11s}: median tau {st.median(taus):+.2f}; 'warning' (tau > 0.5) in {sum(t > 0.5 for t in taus) / 2:.0f} % of series")
# With a shorter record the same rule loses power and gains false alarms
for ramp, lab in ((True, "approaching"), (False, "control")):
taus = [tau_of(series(300, ramp, rnd), win=100, step=5) for _ in range(200)]
print(f"{lab:11s}, only 300 steps: 'warning' in {sum(t > 0.5 for t in taus) / 2:.0f} % of series")
# approaching: median tau +0.91; 'warning' (tau > 0.5) in 100 % of series
# control : median tau -0.04; 'warning' (tau > 0.5) in 7 % of series
# approaching, only 300 steps: 'warning' in 91 % of series
# control , only 300 steps: 'warning' in 14 % of series在这个理想化的例子中,方法表现得相当好:所有「接近」组的序列都发出了预警;但对照组中也有约 7% 发出了预警,而对照组什么都没有变化。数据缩短到 300 步时,检出率降到约 91%,误报率升到约 14%。
这里有两个容易被忽略的条件。第一,例子中的系统确实在缓慢地接近一个临界点,自回归系数是一步步上升的。如果一次突变是由一个足够大的随机冲击把系统从一个稳定状态推到另一个,系统本身的参数并没有变化,就不会有慢化,也就不会有预警。第二,在真实研究中,人们往往先看到了突变,再回头检查之前有没有信号;只检查已经发生突变的序列,而不检查大量没有发生突变的序列,会严重高估方法的可靠性。
证据
预警信号的局限。 伯蒂格与黑斯廷斯用模型说明,在现实的数据长度下,临界慢化指标的检出能力与误报率之间存在难以避免的取舍 (Boettiger and Hastings 2012b);他们在另一篇论文中指出,用只因偶然而发生转变的模拟系统检验,常用的预警统计量会给出过多的假阳性,事后只挑选已经发生转变的系统来检验,会高估方法的可靠性,即「检察官谬误」(Boettiger and Hastings 2012a)。伯蒂格、罗斯与黑斯廷斯也强调,并不是所有发生状态突变的系统都会表现出临界慢化 (Boettiger et al. 2013)。迪克斯、霍默斯与王(Wang)检验了 1987、1997、2000 与 2008 年四次金融危机之前股票市场数据中的临界慢化指标:1987 年「黑色星期一」之前有临界慢化的证据,另外三次的结果则不一致或不显著 (Diks et al. 2019)。
对数周期幂律
索尔内特及其合作者提出了一种更具体的泡沫模型 (Johansen et al. 2000; Sornette 2003)。他们认为,泡沫中的价格不是指数增长,而是比指数更快地增长,趋向一个「临界时间」,并且叠加着越来越快的振荡。对数价格的形式是:
这称为对数周期幂律(LPPL)。这里把振荡写成余弦与正弦两项之和、使非线性参数只剩 、、 三个的写法,来自菲利莫诺夫与索尔内特的改写 (Filimonov and Sornette 2013)。在他们的框架中, 是泡沫最可能结束的时间,而不是崩盘一定发生的时间;泡沫也可以平缓地结束。
这个模型有七个参数,其中 、、 以非线性的方式进入。参数这么多、形式这么灵活,一个自然的担心是:它能不能拟合任何上涨的序列?
推导
8 条没有任何泡沫的模拟序列:带正漂移的随机游走(年化漂移 30%、波动 19%),每条 250 个交易日。对每条序列,在常用的参数范围内( 为最后一天之后 5 到 117 天、步长 8 天, 从 0.1 到 0.9, 从 6 到 13)搜索最佳拟合,并与直线拟合比较;再看同一条随机游走在随后 120 天里最大的 20 日跌幅。
# Over-fitting a log-periodic power law (LPPL; Johansen, Ledoit & Sornette 2000) to series that
# contain NO bubble: random walks with an upward drift (a steady boom, 250 trading days).
# ln p(t) = A + B (tc - t)^m + (tc - t)^m [C1 cos(w ln(tc - t)) + C2 sin(w ln(tc - t))]
# For each (tc, m, w) on a grid, A, B, C1, C2 are fitted by least squares; the best grid point wins.
# Grid: tc 5-117 days after the last observation (steps of 8), m 0.1-0.9, w 6-13 (common filter ranges).
import math, random
def solve(M, v):
n = len(v); M = [row[:] + [v[i]] for i, row in enumerate(M)]
for c in range(n):
piv = max(range(c, n), key=lambda i: abs(M[i][c])); M[c], M[piv] = M[piv], M[c]
if abs(M[c][c]) < 1e-14: return None
for i in range(n):
if i != c:
f = M[i][c] / M[c][c]
M[i] = [a - f * b for a, b in zip(M[i], M[c])]
return [M[i][n] / M[i][i] for i in range(n)]
def lstsq(cols, y):
k = len(cols)
G = [[sum(a * b for a, b in zip(cols[i], cols[j])) for j in range(k)] for i in range(k)]
beta = solve(G, [sum(a * b for a, b in zip(cols[i], y)) for i in range(k)])
if beta is None: return None, float("inf")
fit = [sum(beta[i] * cols[i][t] for i in range(k)) for t in range(len(y))]
return beta, sum((a - b) ** 2 for a, b in zip(y, fit))
def fit_lppl(y):
T = len(y); ts = range(0, T, 2); yy = [y[t] for t in ts]
best = (float("inf"), None)
for tc in range(T + 4, T + 120, 8):
for m in (0.1, 0.3, 0.5, 0.7, 0.9):
for w in (6, 7.5, 9, 10.5, 12, 13):
d = [(tc - t) ** m for t in ts]; L = [math.log(tc - t) for t in ts]
cols = [[1.0] * len(ts), d, [a * math.cos(w * l) for a, l in zip(d, L)], [a * math.sin(w * l) for a, l in zip(d, L)]]
_, sse = lstsq(cols, yy)
if sse < best[0]: best = (sse, (tc - (T - 1), m, w))
tot = sum((u - sum(yy) / len(yy)) ** 2 for u in yy)
_, lin = lstsq([[1.0] * len(ts), [float(t) for t in ts]], yy)
return best[1], 1 - best[0] / tot, 1 - lin / tot
rnd = random.Random(21)
print("series LPPL R^2 straight-line R^2 'critical time' tc largest 20-day fall in the next 120 days")
for k in range(8):
x, y = 0.0, []
for _ in range(370):
x += 0.0012 + 0.012 * rnd.gauss(0, 1); y.append(x) # 30 % a year drift, 19 % a year vol
(tc, m, w), r2, r2lin = fit_lppl(y[:250])
fut = y[249:]
fall = max(1 - math.exp(fut[j] - max(fut[max(0, j - 20):j + 1])) for j in range(len(fut)))
print(f" {k + 1} {r2:.3f} {r2lin:.3f} {tc:4d} days later {fall:5.1%}")
print("No series contains a bubble or a crash mechanism, yet every one yields a fitted 'critical time'.")
# series LPPL R^2 straight-line R^2 'critical time' tc largest 20-day fall in the next 120 days
# 1 0.819 0.766 77 days later 7.6%
# 2 0.812 0.269 109 days later 6.3%
# 3 0.441 0.076 45 days later 15.5%
# 4 0.951 0.925 117 days later 8.9%
# 5 0.739 0.041 37 days later 7.2%
# 6 0.960 0.542 117 days later 10.3%
# 7 0.964 0.958 37 days later 9.4%
# 8 0.971 0.867 5 days later 7.3%
# No series contains a bubble or a crash mechanism, yet every one yields a fitted 'critical time'.每一条序列都得到了一个「临界时间」,从 5 天到 117 天不等,其中几条落在搜索范围的边界上;LPPL 的 都高于直线,多数在 0.8 以上。而随后 120 天的最大 20 日跌幅在 6% 到 16% 之间,与拟合出的「临界时间」毫无关系:这些序列里本来就没有泡沫,也没有崩盘机制。
证据
对 LPPL 的检验与批评。 费根鲍姆对对数周期前兆做了统计分析,对其显著性提出了质疑 (Feigenbaum 2001)。布雷与约瑟夫检验了 1970–2008 年恒生指数的 11 次崩盘:只有 7 次的拟合参数落在约翰森与索尔内特事后给出的范围内;拟合出的曲线常常意味着泡沫期间价格下跌,与模型所依据的机制相矛盾;LPPL 本可以预测 2008 年恒生指数的大幅下跌;总体上数据只部分支持该模型,而且拟合误差对频率参数 高度敏感 (Brée and Joseph 2013)。布雷、沙莱与佩拉诺指出,LPPL 的拟合是「松弛」(sloppy)的:临界时间 是最不确定的参数之一,增减一个数据点就可能使它大幅移动,已发表的预测时间窗口很可能低估了不确定性 (Brée et al. 2010)。
注
事后拟合容易,事前择时极不可靠。 一个灵活的模型能很好地拟合已经发生的上涨,并不说明它抓住了产生上涨的机制;更好的拟合优度很大程度上来自更多的参数。检验一个预警方法,至少需要:在事件发生之前写下预测,包括时间范围;在大量没有发生事件的时期同样运行这个方法,统计误报;并与简单的基准(例如「什么都不预测」)比较。本书不使用任何这类方法给出市场的时间或点位。
假说 · 尚未验证
预警方法的样本外检验假说(尚未确立)。如果一种泡沫或崩盘预警方法真的有用,那么在事先公开登记的样本外检验中,它发出预警之后一定期限内发生大幅回撤的比例,应该明显高于在随机挑选的日期上发出同样数量「预警」时的比例。如果事先登记的检验中两者没有差别,这一方法的预警价值就被否定。
本章小结
临界慢化是一个有坚实理论基础的预警指标:系统缓慢接近临界点时,自相关和方差会上升。模拟显示,在理想条件下它的检出率高,但仍有误报;数据越短,误报越多;由随机冲击触发、系统参数并没有变化的突变,不会留下这种信号。金融危机的实证研究中,只有 1987 年股灾之前有临界慢化的证据,其他几次危机的结果不一致。对数周期幂律能很好地拟合完全没有泡沫的随机游走,并给出任意的「临界时间」。事后拟合容易,事前择时在统计上极不可靠。下一章把这些现代讨论与古代关于盛衰的文字并读。
参考文献
Fan, Y. (2026). Credit Cycles, Self-Organised Criticality and the Statistical Physics of Market Phase Transitions: From Minsky's Financial Instability Hypothesis and Sandpile Power-Law Avalanches to Implied-Volatility Skew, Read Beside Classical Chinese Thought on Rise and Decline [信贷周期、自组织临界性与市场相变的统计物理] (Version 1.0.0). 5D Library. https://doi.org/10.5281/zenodo.23754711 · Licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0).