返回目錄
A
量化投資的智慧:從數據到策略 - 第 3 章
第 3 章 特徵工程與訊號生成
發布於 2026-02-21 17:06
# 第 3 章 特徵工程與訊號生成
本章將聚焦於量化投資流程中最關鍵的前置步驟:**特徵工程**與**訊號生成**。在前兩章中,我們已學會如何蒐集與清洗金融資料;接下來我們將把乾淨的原始資料轉化為可被機器學習模型理解與運用的「特徵」,並進一步衍生出具體的買賣訊號。
---
## 3.1 何謂特徵工程?
> **特徵工程**:將原始資料映射到一組數值或分類特徵的過程。其目標是讓模型能夠「看懂」資料,捕捉市場行為的隱含結構。
| 步驟 | 說明 |
|------|------|
| 資料選取 | 選擇合適的時間週期、產業範疇、交易對象 |
| 變數轉換 | 產生技術指標、基本面比率、情緒分數 |
| 時間特徵 | 建立滯後值、移動平均、季節性特徵 |
| 標準化 | 量級對齊、Z-Score、Min‑Max |
| 交叉驗證 | 防止資訊洩露,確保特徵不含未來訊息 |
## 3.2 技術指標的生成
技術指標(Technical Indicators)是基於歷史價格與成交量推導出的數學函式,常被交易者與量化模型用來捕捉短期動量、趨勢或波動性。下面以 Python 的 `pandas` 與 `ta` 套件為例,示範常用指標的計算。
### 3.2.1 簡易移動平均(SMA)
python
import pandas as pd
from ta.trend import SMAIndicator
# 假設 df 已包含 Date、Close
sma_20 = SMAIndicator(close=df['Close'], window=20).sma_indicator()
sma_50 = SMAIndicator(close=df['Close'], window=50).sma_indicator()
df['SMA20'] = sma_20
df['SMA50'] = sma_50
### 3.2.2 相對強弱指標(RSI)
python
from ta.momentum import RSIIndicator
rsi_14 = RSIIndicator(close=df['Close'], window=14).rsi()
df['RSI14'] = rsi_14
### 3.2.3 布林帶(Bollinger Bands)
python
from ta.volatility import BollingerBands
bb_indicator = BollingerBands(close=df['Close'], window=20, window_dev=2)
df['BBL'] = bb_indicator.bollinger_lband()
df['BBM'] = bb_indicator.bollinger_mavg()
df['BBU'] = bb_indicator.bollinger_hband()
> **提示**:指標數值往往受窗口長短影響,務必在模型驗證階段調整至最佳參數。
## 3.3 基本面資料的特徵化
基本面資料(Fundamental Data)主要來自公司報表、宏觀經濟指標與市場指數。其特徵化方式常包括比率、增長率、季節性修正等。
### 3.3.1 常見財務比率
| 指標 | 計算式 | 典型意義 |
|------|--------|----------|
| P/E (市盈率) | `MarketCap / NetIncome` | 估值水平 |
| EPS (每股盈餘) | `NetIncome / SharesOutstanding` | 獲利能力 |
| ROE (股東權益報酬率) | `NetIncome / Equity` | 資本效率 |
| Debt/Equity | `TotalDebt / Equity` | 財務槓桿 |
Python 範例:
python
# 假設 fin_df 包含 'MarketCap', 'NetIncome', 'Equity', 'TotalDebt'
fin_df['PE'] = fin_df['MarketCap'] / fin_df['NetIncome']
fin_df['EPS'] = fin_df['NetIncome'] / fin_df['SharesOutstanding']
fin_df['ROE'] = fin_df['NetIncome'] / fin_df['Equity']
fin_df['Debt_Equity'] = fin_df['TotalDebt'] / fin_df['Equity']
> **注意**:基本面資料通常更新頻率低(季報或年報),須考量 **時滯** 與 **缺失值** 處理。
## 3.4 新聞情緒與文本特徵
近年來,**自然語言處理(NLP)** 被廣泛應用於量化投資,以捕捉市場情緒與資訊衝擊。以下列出兩種常見實作路徑。
### 3.4.1 文字分數化:VADER
VADER(Valence Aware Dictionary and sEntiment Reasoner)是基於字典的方法,適合短句與社群媒體訊息。
python
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
analyzer = SentimentIntensityAnalyzer()
# 假設 news_df 包含 'Date', 'Headline'
news_df['Sentiment'] = news_df['Headline'].apply(lambda x: analyzer.polarity_scores(x)['compound'])
### 3.4.2 深度模型:BERT + fine‑tune
若資料量充足,可利用 BERT 等預訓練模型進行情緒分類。
python
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')
model = AutoModelForSequenceClassification.from_pretrained('distilbert-base-uncased', num_labels=2)
nlp = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)
news_df['BERT_Sentiment'] = news_df['Headline'].apply(lambda x: nlp(x)[0]['label'])
> **實務建議**:將情緒分數與技術指標結合,形成「多源特徵」。此外,情緒資料通常有 **時間戳** 先後順序,須確保不發生 **look‑ahead bias**。
## 3.5 時間序列特徵的設計
在金融時序資料中,**滯後特徵** (lag features) 與 **滑動窗口統計** 是最常見的技巧。
| 類型 | 範例 | 目的 |
|------|------|------|
| 滯後值 | `Close_t-1`, `Close_t-2` | 捕捉短期自相關 |
| 滑動平均 | `MA_20_t` | 平滑噪音 |
| 滑動標準差 | `Std_20_t` | 波動性估計 |
| 變化率 | `pct_change()` | 觀察變動幅度 |
python
# 生成滯後特徵
lag_cols = ['Close']
for lag in [1, 2, 3]:
df[f'Close_lag{lag}'] = df['Close'].shift(lag)
# 生成滑動窗口統計
df['Close_ma20'] = df['Close'].rolling(window=20).mean()
df['Close_std20'] = df['Close'].rolling(window=20).std()
> **警告**:滯後特徵必須 **向前對齊**,避免「未來資料洩漏」。在回測時,務必使用 **rolling‑window** 或 **expanding‑window** 重新計算特徵。
## 3.6 特徵篩選與降維
在高維度特徵空間,過多的噪音特徵會造成模型過擬合。常見的篩選或降維方法如下。
### 3.6.1 相關係數矩陣 + 互斥篩選
python
corr_matrix = df.corr().abs()
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
# 移除高度相關的特徵 (threshold = 0.9)
to_drop = [column for column in upper.columns if any(upper[column] > 0.9)]
df_filtered = df.drop(columns=to_drop)
### 3.6.2 主成分分析(PCA)
python
from sklearn.decomposition import PCA
features = df_filtered.select_dtypes(include=['float64', 'int64']).columns
pca = PCA(n_components=0.95) # 保留 95% 變異
principal_components = pca.fit_transform(df_filtered[features])
df_pca = pd.DataFrame(principal_components, columns=[f'PC{i+1}' for i in range(pca.n_components_)])
> **實務觀察**:降維往往更適合 **回歸** 或 **多分類** 模型;若採用 **線性模型** 或 **決策樹**,則可考慮直接篩選關鍵特徵。
## 3.7 訊號生成策略
### 3.7.1 直接閾值法
> **範例**:若 `SMA20 > SMA50` 且 `RSI14 < 30`,則生成「買入」訊號;若 `SMA20 < SMA50` 且 `RSI14 > 70`,則生成「賣出」訊號。
python
condition_buy = (df['SMA20'] > df['SMA50']) & (df['RSI14'] < 30)
condition_sell = (df['SMA20'] < df['SMA50']) & (df['RSI14'] > 70)
df['Signal'] = 0
df.loc[condition_buy, 'Signal'] = 1 # 1: 買
df.loc[condition_sell, 'Signal'] = -1 # -1: 賣
### 3.7.2 機器學習預測方向
我們可以把 **二元分類** 看作「預測未來收盤價的方向」。
python
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import roc_auc_score
features = ['SMA20', 'SMA50', 'RSI14', 'Close_lag1', 'Close_ma20', 'Close_std20', 'Sentiment']
X = df[features].fillna(0)
# 目標:未來收盤價相對於今日是否上漲
y = (df['Close'].shift(-1) > df['Close']).astype(int)
tscv = TimeSeriesSplit(n_splits=5)
roc_scores = []
for train_idx, test_idx in tscv.split(X):
X_tr, X_te = X.iloc[train_idx], X.iloc[test_idx]
y_tr, y_te = y.iloc[train_idx], y.iloc[test_idx]
model = LogisticRegression(max_iter=200)
model.fit(X_tr, y_tr)
prob = model.predict_proba(X_te)[:, 1]
roc_scores.append(roc_auc_score(y_te, prob))
print('平均 AUC:', sum(roc_scores) / len(roc_scores))
> **最佳實務**:將訊號的 **生成、權重分配** 與 **回測** 緊密耦合,確保「訊號 + 模型」的完整評估流程。
## 3.8 綜合示例:多源特徵訊號策略
下面是一個完整的特徵工程‑訊號‑回測流程,使用 `bt` 套件簡化。
python
import bt
# 1. 整合所有特徵
feature_df = pd.concat([df, fin_df, news_df[['Sentiment']]], axis=1)
feature_df = feature_df.dropna()
# 2. 建立訊號 (簡易 3‑分級) |
# 3.1 方向分數 (技術 + 基本 + 情緒)
feature_df['Score'] = (
0.4 * feature_df['RSI14'] +
0.3 * feature_df['Sentiment'] * 100 +
0.3 * (feature_df['Close_ma20'] - feature_df['Close'])
)
# 3.2 產生買賣訊號
feature_df['Signal'] = 0
feature_df.loc[feature_df['Score'] > 0.6, 'Signal'] = 1
feature_df.loc[feature_df['Score'] < -0.6, 'Signal'] = -1
# 4. 回測
strategy = bt.Strategy('multi_source', [
bt.algos.WeighTarget('Signal'),
bt.algos.Rebalance()
])
portfolio = bt.Backtest(strategy, feature_df['Close'])
result = bt.run(portfolio)
result.plot(title='多源特徵訊號回測績效')
> **關鍵點**:
> 1. **資訊洩漏**:特徵計算必須在「歷史資料」上完成,且對於每個測試時期重新計算。
> 2. **Look‑ahead Bias**:任何包含未來價格或未公布訊息的特徵都會導致績效膨脹。
> 3. **多重共線性**:不同來源的特徵(如 RSI 與 Bollinger Band 的相對位置)可能高度相關,建議進行特徵篩選。
---
## 3.9 小結
本章提供了以下幾點關鍵步驟與實務技巧:
| 步驟 | 核心技巧 |
|------|----------|
| 產生技術指標 | 使用 `ta` 套件,調整窗口長短 |
| 處理基本面 | 計算財務比率、考慮時滯 |
| 文本情緒 | VADER 或 BERT,注意時間對齊 |
| 時間特徵 | 滯後值、滑動窗口統計,嚴格防止洩漏 |
| 特徵篩選 | 相關係數、PCA 或 L1 正則化 |
| 訊號生成 | 閾值法、機器學習預測,結合多源特徵 |
在實際的量化投資項目中,特徵工程往往是模型成功與否的「關鍵門檻」。正確的設計、嚴格的對齊檢查與持續的特徵演進,將為後續的機器學習模型與交易策略奠定堅實基礎。