跳转至


课程  因子投资  机器学习  Python  Poetry  ppw  tools  programming  Numpy  Pandas  pandas  算法  hdbscan  聚类  选股  Algo  minimum  numpy  algo  FFT  模式识别  配对交易  GBDT  LightGBM  XGBoost  statistics  CDF  KS-Test  monte-carlo  VaR  回测  过拟合  algorithms  machine learning  strategy  python  sklearn  pdf  概率  数学  面试题  量化交易  策略分类  风险管理  Info  interview  career  xgboost  PCA  wavelet  时序事件归因  SHAP  Figures  Behavioral Economics  graduate  arma  garch  人物  职场  Quantopian  figure  Banz  金融行业  买方  卖方  story  量化传奇  rsi  zigzag  穹顶压力  因子  ESG  因子策略  投资  策略  pe  ORB  Xgboost  Alligator  Indicator  factor  alpha101  alpha  技术指标  wave  quant  algorithm  pearson  spearman  tushare  因子分析  Alphalens  涨停板  herd-behaviour  momentum  因子评估  review  SMC  聪明钱  trade  history  indicators  zscore  波动率  强化学习  顶背离  freshman  resources  others  AI  DeepSeek  network  量子计算  金融交易  IBM  weekly  LLT  backtest  backtrader  研报  papers  UBL  quantlib  jupyter-notebook  scikit-learn  pypinyin  qmt  xtquant  blog  static-site  duckdb  工具  colors  free resources  barra  world quant  Alpha  openbb  数据  risk-management  llm  prompt  CANSLIM  Augment  arsenal  copilot  vscode  code  量化数据存储  hdf5  h5py  cursor  augment  trae  Jupyter  jupysql  pyarrow  parquet  数据源  quantstats  实盘  clickhouse  notebook  redis  remote-agent  AI-tools  Moonshot  回测,研报,tushare 

factor&strategy »

当交易员用上火箭科学!波和导数检测出艾略特浪、双顶及及因子构建


这篇文章的部分思想来自于 John Ehlers。他曾是雷神的工程师,当年是为NASA造火箭的。他有深厚的数字信号处理(DSP)技术背景,为石油钻探发明了最大熵频谱分析(MESA)。这种分析方法能为短暂的地震回波提供高分辩率的显示。

随后,他把这种方法应用到金融领域,并开创了名为MESA的软件公司,为交易者提供相关分析软件和教育服务。

他和JM Hurst是在证券的周期分析上贡献最大的几人之一。John Ehlers还发表了许多专著,包括《交易者的火箭科学》等。

不过,直到1989年Python才被发明,直到千禧年左右才广为人知,所以,John Ehlers的许多思想,是使用一种所谓的Easy Language表达的。我们尝试使用Python来传达他的一些思想,并加入了自己的理解与拓展。最后,我们将介绍基于这种思想,发现的一个因子。

毫无疑问,证券价格是一种变形的周期信号。它的基本走势由公司的价值决定,叠加交易产生的波动。

加速成长的公司,比如,正在浪头上的科技股,它们的价值曲线可能是指数函数,比如90年代的思科、2000年前后的微软,后来的苹果和现在的英伟达。基础服务类的公司,它们的价值曲线应该是参照GDP增长的直线。

Info

说到苹果,最近发布的 Mac Mini 4是相当不错。正在抢购中。几年前入手的 Macbook M1压缩mp4视频能达到6:1的加速比,Mac Mini 4估计加速比能到30:1甚至更高了,也就是1小时的影片,应该不到2分钟完成编码压缩完成。

波动则由不同风格、不同资金管理周期(其倒数即为频率)的投资者驱动。这两类曲线合成了最终的走势,并且重大事件将改变周期规律。

下面这个函数将生成一个震荡向上的价格序列。它由一条向上的直线和一个sine曲线合成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def gen_wave_price(size, waves, slope, amp = 0.3, phase=0):
    time = np.arange(size)
    amp_factor = time * amp
    freq = 2 * np.pi * waves / size

    y = amp_factor * np.sin(freq * time + phase) + time * slope
    return y

y = gen_wave_price(100, 3, 0.5, 0.25)
plt.plot(np.arange(100), y)

这将生成以下图形:

图1. 震荡向上走势

实际上,我们已经合成了一个艾略特驱动浪:

艾略特上升5浪

合成双顶和头肩顶

关于双顶和头肩顶,我有很好的检测算法。不过,如果你对波更感兴趣的话,我可以用sine函数来捏一个。

1
2
3
4
x = np.linspace(1, 10, 100)
y = np.sin(x) + .33 * np.sin(3*x)

plt.plot(x, y)
图2 双头

头肩顶只需要再加一个sine波:

1
2
3
4
x = np.linspace(1, 10, 100)
y = np.sin(x) + .1* np.sin(3*x) + .2 * np.sin(5*x)

plt.plot(x, y)
图3 头肩顶

如果要检测股价序列中,是否存在双头,我们可以使用scipy中的curve_fit函数。既然\(np.sin(x) + .33 * np.sin(3*x)\)能拟合出双头,那么,如果我们能从价格序列中,通过curve_fit找出类似的函数,再判断参数和估计误差,就能判断是否存在双头。

这个函数泛化后,应该表示为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from scipy.optimize import curve_fit, differential_evolution

# 待推断函数
def model(x, a, b, c, d):
    return a * np.sin(b * x) + c * np.sin(d * x)

# 目标函数
def objective(params):
    a, b, c, d = params
    y_fit = model(x, a, b, c, d)
    error = np.sum((y - y_fit) ** 2)
    return error

# 推断和绘图
def inference_and_plot(x, y):
    bounds = [(0, np.max(y)), (0, len(x)), (0, 1), (0, len(x))]
    result = differential_evolution(objective, bounds)

    initial_guess = result.x
    params, params_covariance = curve_fit(model, x, y, p0=initial_guess)

    plt.plot(x, y)
    plt.plot(x, model(x, *params), 
             label='拟合曲线', 
             color='red', 
             linewidth=2)
    return params, params_covariance


# 增加一点噪声
x = np.linspace(1, 10, 100)
y = np.sin(x) + .33* np.sin(3*x)
y += np.random.normal(0.05, 0.1, size=100)
inference_and_plot(x[60:], y[60:])
图4 双头推断

这里使用了差分进化算法来自动发现参数。

我们把蓝色部分当成是真实的股价序列,红色曲线就是通过差分进化算法推断出来的拟合曲线。

波和导数

到目前为止,我们还只进行了铺垫和前戏,只是为了说明股价序列确实具有波的特性。既然如此,我们把波的一些特性给用起来。

正弦波有一个有意思的特性,就是它的导数是一个余弦波,即:

\(\frac{d}{dt}sin(\omega t)=(\frac{1}{\omega})\times cos(\omega t)\)

求导之后,变成频率相同的余弦波,而余弦波是相位提前的正弦波。我们来验证一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
t = np.linspace(1, 10, 100)

# 原函数
y = np.sin(t)

# 导函数
dy = np.diff(y)
dy = np.insert(dy, 0, np.nan)

fig, ax = plt.subplots(figsize=(10,5))
line1, = ax.plot(y, label="原函数")

ax2 = ax.twinx()
ax2.grid(False)
line2, = ax2.plot(dy, '-', color='orange', label="导数")

lines = [line1, line2]
labels = [line.get_label() for line in lines]
plt.legend(lines, labels, loc="upper right")
plt.show()
图5 导函数与原函数关系

蓝色的线是原来的股价,黄色的则是它的导数。从图中可以看出,求导之后,频率不变,振幅变小了,相位提前,并且黄色线的每一个波峰和波谷,后面都对应着蓝色线的波峰和波谷。这意味着什么呢?

如果原函数是一个波函数,现在,我们就能提前预测波峰和波谷了。上图清楚地显示了这一点。

几乎所有的技术指标都是落后指标,但是,导函数居然帮我们预言了波峰与波谷的到来!

Tip

不要感到震惊!其实很多量化人都已经在一种浑然不知的状态下使用这个特性了。比如,你几乎肯定用过三周期的np.diff再加np.mean。只要你用了np.diff数据,就在某种程度上使用了导数。但是,清楚地知道这一特性,我们才知道何时使用、何时放弃。

让我们把话说得再明白一点:

如果原函数是一个波函数,那么,通过寻找导函数的波峰与波谷(这是利用已经发生的数据),就能提前1/4周期知道原函数何时到波峰与波谷 -- 惟一的前提是,规律在这么短的时间里,不发生改变 -- 不会总是这样,但总有一些时间、一些品种上,规律确实会保持,而你要做的,就是运用强大算力,尽快发现它们。

我们将在《因子分析与机器学习策略》课程中,将这个特性转换成一个特征,加入到机器学习模型中,以提高其对顶和底的预测能力。课程还在进行中,现在加入,会是未来几个月中,价格最低的时候。

课程助理小姐姐在这里等你!

不过,在构建机器学习模型之前,我们可以先用这个特性构建一个因子。

点击查看源码

我使用2018年到2023年间随机抽取的2000支个股,进行了因子验证,得到以下结果:

1D 5D 10D
Ann. alpha 0.138 0.096 0.078
beta 0.036 0.064 0.058
Mean Period Wise Return Top Quantile (bps) -1.180 -1.613 -1.580
Mean Period Wise Return Bottom Quantile (bps) -15.671 -11.045 -9.155
Mean Period Wise Spread (bps) 14.491 9.531 7.650

6年间的累计年化收益图如下:

图6 累计收益图

这个收益还没有进行优化。实际上,从下面的分层收益均值图来看,是可以优化的。

图7 分层收益均值图

我们可以在因子检验之前,过滤掉第10层的因子。这样处理之后,我们得到的年化Alpha将达到20.08%,6年累计收益接近3倍。

即使是纯多策略,该因子的年化Alpha也达到了14%。

因子构建及验证代码加入星球后即可获得。

Recurring Phase of Cycle Analysis

John Ehler在这篇文章里,提出了寻找周期的方法。

图8 John Ehlers's Recurring Phase Analysis

代码使用的是Easy Languange,不过这门语言对我来说一点也不easy。我不知道它是如何实现FFT的,并且还有无处不在的魔术数字。

不过,我完成了一个类似的研究。这个研究旨在揭示fft变换之后的直流分量的意义。

点击查看源码

对图1中的时间序列进行fft变换后,去掉直流分量,再逆变换回来,将两者进行对比,我们会得到这样的一个图:

图9 去掉直流分量的对比

非常有意思。

第一点,橙色的线是去掉直流分量后的序列(的实部)。它与原序列之间的差值是一个常数,这个常数竟然是原序列的均值!

1
assert np.mean(y) == np.abs(y - y2)

这就是直流分量的真实含义。从数学上讲是一个均值,从交易上讲,它是公司的定价,一切波动都在围绕它发生。

第二点,y与y2的模的差分有上下界。当股价上涨到一定程度之后,一部分能量被浪费在虚部的方向上,该方向是与实部正交的方向,从而导致这个差分有上下界。这似乎是图8中,John Ehler要揭示的信息。

如果一个函数有上下界,它对交易的帮助就太大了。