课程  因子投资  机器学习  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  穹顶压力  因子  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 

量化策略中如何进行缩放和归一化


Table of Content

缩放和归一化是机器学习中必然遇到的一个问题。在量化策略中,不管是否用到机器学习,我们也常常遇到类似问题。

在《大富翁量化金融实战课》第12课中,对这个问题进行了较深入的讨论。这篇文章既是对该课内容的一个摘要,也是加入了一些新的思考,可以独立成篇。

从数学上看,缩放(归一化)是从一个值域映射到另一个值域的过程。这种映射,有的是线性的(比如min-max scale),有时候则需要非线性映射,比如,我们常常需要将一个\((-\infin, +\infin)\)的区间映射到\((-1, 1)\)或者\((0, 1)\)上。

从量化交易的角度看,WR指标(William's R)就是一个典型的min-max scale,它的公式如下:

\[ WR = \frac{max(high) - close}{max(high) - min(low)} \]

Attention

在量化交易中使用min-max scale一定要非常小心。
它主要存在两个问题。
其一是它引入了未来数据(但不是在所有情况下都会引入未来数据,比如William's R在实时计算时,是不存在未来数据的;但在回测中是否引入未来数据,就要看你怎么用了。
其二是,使用经过min-max scale处理的数据来进行预测,预测值一定在[min, max]区间内。但是,股价不存在确定的min/max。如果我们根据当下经过min-max scale处理后的数据来预测未来的股价,预测出来的股价是不可能突破当下见到的最大值的,而实际上,股价是可以涨上天的。
与之对比,在鸢尾花数据集中,我们对花瓣的长度和宽度都可以进行min-max scale,因为我们的采样基本上能够代表总体,未来也基本上不会出现明显超出采样中的min/max的。

更多的时候,在量化中我们需要进行非线性映射。比如,我们曾经介绍过,地量是一种比较重要的交易信号。地量见地价。什么是地量?我们可以用当前成交量是多少个周期以来的最低值来度量。这个数值最小为1,最大可以是10天,也可以是一个月,一年或者更长,因此,它的取值范围从数学上看是\([1, +\infin]\)

在第13课,我们介绍圆弧底的评价函数时,也讲到过圆弧底的宽度区间可能在\([3, +\infin)\)之间。

对这种值域范围不确定的,我们一般可以用S型函数来进行变换。这些函数包括:

\[ f(x) = sigmoid(x) = \frac{1}{1 + e^{-x}} \tag 1 \]
\[ f(x) = tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} \tag 2 \]
\[ f(x) = \frac{x}{1+|x|} \tag 3 \]
\[ f(x) = \frac{x}{sqrt(1 + x^2)} \tag 4 \]

特殊情况下,如果我们的采样数据存在周期性特点,而且我们希望在变换后,保留它们在周期上的关联性,也可以用\(sin\)\(cos\)函数。这种归一化方法,在著名的《attention is all you need》一文中就使用过(对token的位置进行编码):

10%

考虑到股价波动的周期性,也许类似的方法也应该在量化中找到运用。

但是,使用sigmoid函数来进行归一化也存在一些问题。比如在地量那个例子中,13个周期以来的地量,与120天以来的地量相比,显然两者具有完全不同的信号含义:一支个股很容易出现13个周期以内的地量,而很难出现120个周期以内的地量。一旦出现,很容易出现一定幅度的反弹。

然而,如果我们对其进行sigmoid归一化的话,它们的取值将会是一样的:

1
2
3
4
def sigmoid(x):
    return 1/(1 + np.exp(-x))

print("sigmoid(12) == sigmoid(13)?", np.isclose(sigmoid(12), sigmoid(13)))
从上例可以看出,实际上,当\(x > 12\)时,在计算中我们就已经无法区分函数值之间的差异了。

因此,在使用中,我们一般要对sigmoid进行一些修改,使之在某个区间内,具有较高的响应灵敏度。公式如下:

\[ f(x) = \frac{2}{1+e^{-\frac{ln(40000)}{b}.(x-b)+ln(0.005)}} - 1 \]

此时图形主要由参数b决定。当b为15时,图形如下:

50%

该分布区间为[-1, 1],x取值在[0,b]之间时分布密度较高。

在非机器学习型的量化打分策略中,我们可能倾向于函数值域落在[0,1]之间(想想看,RSI和WR都在这个区间内,而不是[-1,1])。所以,我们对上述公式略加修改,代码实现如下:

 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
import matplotlib.pyplot as plt

def scaled_sigmoid(x, start, end):
    """当`x`落在`[start,end]`区间时,函数值为[0,1]且在该区间有较好的响应灵敏度
    """
    n = np.abs(start - end)

    score = 2/(1 + np.exp(-np.log(40_000)*(x - start - n)/n + np.log(5e-3)))
    return score/2


fig, (ax1, ax2, ax3,ax4) = plt.subplots(nrows = 1, ncols = 4, figsize=(12,3))

x = np.linspace(0, 1)
ax1.plot(x, [scaled_sigmoid(i, x[0], x[-1]) for i in x])
ax1.set_title("fit (0,1)")

x = np.linspace(0, 100)
ax2.plot(x, [scaled_sigmoid(i, x[0], x[-1]) for i in x])
ax2.set_title("fit (0, 100)")

x = np.linspace(18, 38)
ax3.plot(x, [scaled_sigmoid(i, x[0], x[-1]) for i in x])
ax3.set_title("fit (18, 38)")

x = np.linspace(0, 100)
ax4.plot(x, [sigmoid(i) for i in x])
ax4.set_title("fit (0,100) with original")

从上图可以看出,与原来的sigmoid相比,新的scaled_sigmoid函数在[start, end]区间有很好的响应灵敏度,此区域内的分布密度最高。