5種低波動因子,高效策略快速實踐

低波動因子

什麼是低波動因子?

低波動因子是一種量化指標,用來衡量股票或資產在給定期間內的價格波動程度。這些因子通常用於選擇那些價格波動相對較小的股票,並構建低風險的投資組合。常見的低波動因子包括標準差、平均真實波幅(ATR)、最大回撤等。

為什麼要使用低波動因子?

使用低波動因子的主要原因包括:

  1. 風險管理:低波動因子有助於識別和選擇價格波動較小的股票,從而減少投資組合的整體波動性,降低投資風險。
  2. 穩定回報:歷史數據顯示,低波動股票在市場下跌時通常表現較好,能提供更穩定的回報 。
  3. 投資者行為偏差:投資者往往追求高風險高回報的股票,而忽視了低風險的股票。利用低波動因子可以逆勢投資,獲得超額收益 。

如何搭配低波動因子?

在實際應用中,可以將低波動因子與其他因子結合使用,以進一步優化投資組合。例如:

  1. 多因子模型:將低波動因子與動量因子、價值因子等結合,構建多因子模型,選擇綜合得分最高的股票進行投資。
  2. 權重分配:根據低波動因子的得分,對投資組合中的股票進行權重分配,增加低波動股票的權重,減少高波動股票的權重。
  3. 動態調整:定期重新計算和調整投資組合中的低波動因子,保持投資組合的穩定性和優化效果。

學術研究支持

低波動因子在優化投資組合方面的效果已在多項學術研究中得到證實。例如:

透過這些研究,可以更好地理解低波動因子的應用價值及其在投資組合優化中的重要性。

因子的選擇與計算

在低波動策略中,我們會使用多種因子來衡量股票的波動性,以下是一些常見的低波動因子及其數學公式與計算方法:

NATR (Normalized Average True Range)

定義: NATR 是 ATR (Average True Range) 的標準化版本,用來衡量股票價格波動幅度的平均值。

計算公式:

\text{NATR} = \left( \frac{\text{ATR}}{\text{Closing Price}} \right) \times 100

其中,ATR (Average True Range) 的計算方式如下:

\text{TR} = \max(\text{High} - \text{Low}, |\text{High} - \text{Previous Close}|, |\text{Low} - \text{Previous Close}|)

\text{ATR} = \frac{\sum_{i=1}^{n} \text{TR}_i}{n}

TR 表示真實波幅,ATR 表示在 n 天內的平均真實波幅。

標準差 (Standard Deviation)

定義: 標準差用來衡量股票收益率在特定時間窗口內的波動程度。

計算公式:

\text{STD}n = \sqrt{\frac{1}{n-1} \sum{i=1}^{n} (R_i - \bar{R})^2}

其中,R_i​ 是第 i 天的收益率,\bar{R} 是 n 天內的平均收益率。

最低/最高比 (Min/Max Ratio)

定義: 最低/最高比用來衡量股票價格在特定時間窗口內的最低值與最高值之間的比率。

計算公式:

\text{MINMAX}_n = \frac{\min(P_i)}{\max(P_i)} - 1

其中,P_i​ 是第 i 天的收盤價。

最大回撤 (Maximum Drawdown)

定義: 最大回撤用來衡量在特定時間窗口內,股票價格從峰值下跌到谷底的最大跌幅。

計算公式:

\text{MDD}_n = \frac{\min(P_i)}{\max(P_i)} - 1

其中,P_i​ 是第 i 天的收盤價。

1/夏普比率 (1/Sharpe Ratio)

定義: 夏普比率用來衡量風險調整後的收益,1/夏普比率是其倒數,用來反映單位風險下的收益波動程度。

計算公式:

\text{Sharpe Ratio}_n = \frac{\bar{R} - R_f}{\text{STD}_n}

其中,\bar{R} 是 n 天內的平均收益率,R_f​ 是無風險利率,$latex \text{STD}_n​ 是 n 天內的收益率標準差。

這些因子都可以用來衡量股票的波動性,並且可以根據它們的歷史表現來選擇合適的因子進行投資決策。

以下是使用 Python 來計算的程式碼範例:

import numpy as np
import pandas as pd
from finlab import data

# 獲取調整後的收盤價
adj = data.get('etl:adj_close')

# 計算低波動因子
indicators = {
    'NATR14': data.indicator('NATR', timeperiod=14),
    'NATR60': data.indicator('NATR', timeperiod=60),
    'STD14': adj.pct_change().rolling(14).std(),
    'STD60': adj.pct_change().rolling(60).std(),
    'MINMAX14': adj.rolling(14).min() / adj.rolling(14).max() - 1,
    'MINMAX60': adj.rolling(60).min() / adj.rolling(60).max() - 1,
    'MDD14': (adj / adj.cummax()).rolling(14).min() - 1,
    'MDD60': (adj / adj.cummax()).rolling(60).min() - 1,
    '1/SHARPE14': adj.pct_change().rolling(14).std() / adj.pct_change().rolling(14).mean(),
    '1/SHARPE60': adj.pct_change().rolling(60).std() / adj.pct_change().rolling(60).mean(),
}

因子IC值的計算與判斷

Information Coefficient (IC) 是一個衡量因子預測能力的指標,主要用來評估某一因子與未來股票收益率之間的相關性。具體而言,IC 是因子值與未來某一段時間內股票收益率之間的橫截面相關係數。

IC 值提供了因子有效性的度量:

  • 正 IC 值:表示因子值與未來收益率之間有正相關性,因子值越高,未來收益率越高。這意味著因子具有良好的預測能力。
  • 負 IC 值:表示因子值與未來收益率之間有負相關性,因子值越高,未來收益率越低。這同樣意味著因子具有預測能力,但方向相反。
  • IC 值接近 0:表示因子值與未來收益率之間沒有顯著的相關性,因子預測能力較差。

以下是計算IC值的Python代碼:

from tqdm import tqdm

def ic(factor, adj_close, days=[10, 20, 60, 120]):
    if isinstance(factor, pd.DataFrame):
        factor = {'factor': factor}

    for fname, f in factor.items():
        factor[fname] = FinlabDataFrame(f).index_str_to_date()

    first_ele = next(iter(factor))
    findex = factor[first_ele].index
    fcol = factor[first_ele].columns

    ind = factor.copy()
    adj = adj_close.copy()

    inter_index = findex.intersection(adj.index)
    inter_col = fcol.intersection(adj.columns)
  
    adj = adj.loc[inter_index]
    adj = adj[inter_col]

    for fname, f in factor.items():
        ind[fname] = ind[fname].loc[inter_index]
        ind[fname] = ind[fname][inter_col]

    ics = {}

    total = len(days) * len(factor)
    with tqdm(total=total, desc="Processing") as pbar:
        for d in days:
            ret = adj.shift(-d-1) / adj.shift(-1) - 1

            for fname, f in ind.items():
                ic = f.apply(lambda s: s.corr(ret.loc[s.name]), axis=1)
                ics[f"{fname}_{d}"] = ic
                pbar.update(1)

    return pd.concat(ics, axis=1)

ics = ic(indicators, adj)
print(ics.mean().sort_values(ascending=False))
image 1

如此一來就可以來計算每個指標的有效程度。由此可見,NATR60 對於預測 120 天後的價格,有比較顯著的優勢。

策略製作

接下來,我們可以用上述的因子,來篩選「穩定的飆股」,這段程式碼的目的是基於某一個因子的選股策略進行回測,並通過繪製策略與基準的日收益率曲線來評估該策略的表現。以下是對這段程式碼的詳細解釋和其背後的思路:

1. 選擇IC值最高的因子

best_factor = 'NATR60'

在這一步中,我們選擇 IC 值最高的因子 NATR60。這意味著在之前的因子評估中,NATR60 是最能預測未來股票收益的因子。因此,我們將其作為選股策略的基礎。

2. 計算持倉

position = (adj / adj.rolling(120).mean())[
    (indicators[best_factor].rank(axis=1, pct=True) < 0.5)
].is_largest(30)

這一步的目的是根據選定的因子來確定投資組合中的持倉股票。具體步驟如下:

  1. 均線回歸adj / adj.rolling(120).mean() 計算股票價格與其120日均線的比值。這樣可以幫助我們識別相對於均線偏離程度的股票。
  2. 因子排序indicators[best_factor].rank(axis=1, pct=True) 對選定的因子值按橫截面進行排序,並轉換為百分位數。rank(axis=1, pct=True) 將因子值轉換為介於0到1之間的百分位數。
  3. 篩選股票(indicators[best_factor].rank(axis=1, pct=True) < 0.5) 篩選出因子值在前50%的股票(百分位數小於0.5)。
  4. 選擇最大持倉is_largest(30) 在篩選出的股票中,選擇30個相對均線偏離最大的股票進行持倉。

最終結果 position 是一個二元矩陣,表示在每個日期應持有的股票。

3. 回測策略

result = sim(position.loc['2014':], resample='Q', upload=False)

在這一步,我們利用 finlab.backtest 庫的 sim 函數對策略進行回測。具體參數說明:

  • position.loc['2014':]:選擇從2014年開始的持倉數據進行回測。
  • resample='Q':設置回測結果按季度頻率重採樣。
  • upload=False:禁用結果上傳功能。

sim 函數會模擬策略在歷史數據中的表現,並返回包含策略結果的對象。

4. 繪製收益率曲線

result.display()
image 2

這樣可以直觀地對比策略與基準的表現,幫助我們評估策略的有效性。

整體思路與效果

這段程式碼的核心思路是基於低波動因子 NATR60 進行選股,並通過回測來評估該因子的投資效果。具體步驟包括:

  1. 選擇預測能力最強的因子作為策略基礎。
  2. 利用均線回歸和因子排序來確定持倉股票。
  3. 對策略進行歷史回測,評估其收益與風險特徵。
  4. 繪製策略與基準的收益率曲線,直觀對比策略表現。

總結

這個策略結合了因子分析、均線回歸和選股排名的方法,形成了一個多層次的選股和投資框架。每一步驟都基於歷史數據和統計方法,從而提高了策略的有效性和穩定性。通過這樣的方法,我們可以在風險可控的情況下,獲得較為穩定的超額收益。

FinLab - 韓承佑

嗨大家好,我是韓承佑,FinLab創辦人,畢業於巴黎薩克雷大學資工博士,目前擔任臺灣量化交易協會 學術顧問、台北商業大學 創新育成中心 創業技術顧問與上市科技公司 量化交易顧問。當初,我喜歡寫程式、無意間因為軟體比賽接觸Fintech,從此開始了財經跟程式的學習之路。我們成立 FinLab 量化投資部落格,用自己研發的軟體,對台灣股市做大量快速的實驗。希望可以在量化投資的路上,當大家的「武器製造商」!