宏观数据清洗,以及增长/通胀/利率等宏观因子、NLP与高频因子。
构建量化模型时,核心难点往往不在算法或策略本身,而在数据。
在宏观因子研究中,数据工作通常占用大量时间。模型设定再完善,若输入数据质量不足,结论也难以可靠。因此,数据获取与清洗构成整个系统的基础。
宏观数据常见来源如下,均为实务与研究中较常使用的渠道。
央行的数据,主要是货币政策相关的。比如:
上述数据一般可在央行官网统计数据栏目获取。为提高效率,实务中多采用 Python 抓取或调用现成数据接口。
统计局的数据,覆盖面最广。比如:
国家统计局官网提供 Excel 下载,但表格格式可能随年份变化。不同年份的数据结构不一致是常见问题,后文将说明处理方法。
Wind 是国内金融机构常用的数据终端,覆盖面广、更新较快,但使用成本较高。机构用户通常可获得账号。Wind 提供 Python 接口(WindPy),可直接调用数据。
在预算允许的条件下,优先使用 Wind 有助于提高数据质量并降低清洗成本。
确定数据源后,可用 Python 获取数据。较常用的工具包括:
这个库是 pandas 的扩展,专门用来获取金融数据。支持 FRED、世界银行、Yahoo 等。
import pandas_datareader.data as web
import datetime
# 从 FRED 获取美国 GDP 数据
start = datetime.datetime(2000, 1, 1)
end = datetime.datetime(2024, 12, 31)
gdp = web.DataReader('GDP', 'fred', start, end)
print(gdp.head)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-1-337883f6a6bd> in <cell line: 0>() ----> 1 import pandas_datareader.data as web 2 import datetime 3 4 # 从 FRED 获取美国 GDP 数据 5 start = datetime.datetime(2000, 1, 1) ModuleNotFoundError: No module named 'pandas_datareader'
FRED 宏观指标较为全面,常用于获取美国利率、失业率等数据。
yfinance 是 Yahoo Finance 的 Python 接口。虽然 Yahoo 在国内访问不太稳定,但用来获取美股、ETF 数据还是不错的。
import yfinance as yf
# 获取标普500指数数据
sp500 = yf.download('^GSPC', start='2000-01-01', end='2024-12-31')
print(sp500.head)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-2-c3b326dfd406> in <cell line: 0>() ----> 1 import yfinance as yf 2 3 # 获取标普500指数数据 4 sp500 = yf.download('^GSPC', start='2000-01-01', end='2024-12-31') 5 print(sp500.head()) ModuleNotFoundError: No module named 'yfinance'
注意:yfinance 返回的是 DataFrame,包含开盘、收盘、最高、最低、成交量等字段。做因子模型时,通常使用收盘价计算收益率。
akshare 为最推荐的国内数据工具。它封装了央行、统计局、交易所等几十个数据源,接口统一,用起来很爽。
import akshare as ak
# 获取中国 GDP 数据
gdp_china = ak.macro_china_gdp
print(gdp_china.head)
# 获取 CPI 数据
cpi = ak.macro_china_cpi_monthly
print(cpi.head)
季度 国内生产总值-绝对值 国内生产总值-同比增长 第一产业-绝对值 第一产业-同比增长 第二产业-绝对值 \
0 2026年第1-2季度 695704.0 4.7 31521.8 3.7 250472.9
1 2026年第1季度 334192.9 5.0 11940.8 3.8 116134.9
2 2025年第1-4季度 1401879.2 5.0 93346.8 3.9 499653.0
3 2025年第1-3季度 1013967.9 5.2 58187.1 3.8 362849.7
4 2025年第1-2季度 659861.6 5.3 31234.8 3.7 238262.6
第二产业-同比增长 第三产业-绝对值 第三产业-同比增长
0 3.9 413709.2 5.2
1 4.9 206117.2 5.2
2 4.5 808879.3 5.4
3 4.9 592931.1 5.4
4 5.3 390364.3 5.5
商品 日期 今值 预测值 前值
0 中国CPI月率报告 1996-02-01 2.1 NaN NaN
1 中国CPI月率报告 1996-03-01 2.3 NaN 2.1
2 中国CPI月率报告 1996-04-01 0.6 NaN 2.3
3 中国CPI月率报告 1996-05-01 0.7 NaN 0.6
4 中国CPI月率报告 1996-06-01 -0.5 NaN 0.7
akshare 的文档很详细,基本上得到的宏观数据,它都有。进行过一个项目,需要同时拉取 M2、CPI、工业增加值、PMI 四个指标,用 akshare 一行一个,十分钟完成。
数据拿到手,不宜立即建模。先检查数据基本形态。常见情况是,数据都没看清楚,直接估计模型,结果结果缺乏可靠性。
宏观数据经常有缺失。比如 GDP 是季度数据,CPI 是月度数据,频率不一样。合并的时候,低频数据会有很多空值。
import pandas as pd
# 假设有两个 DataFrame:gdp(季度)和 cpi(月度)
# 合并后,GDP 列会有大量 NaN
merged = pd.merge(gdp, cpi, on='date', how='outer')
# 方法1:向前填充(用上一个值填充)
merged['gdp'].fillna(method='ffill', inplace=True)
# 方法2:插值法
merged['gdp'].interpolate(method='linear', inplace=True)
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-4-0e13f31c3dc7> in <cell line: 0>() 3 # 假设有两个 DataFrame:gdp(季度)和 cpi(月度) 4 # 合并后,GDP 列会有大量 NaN ----> 5 merged = pd.merge(gdp, cpi, on='date', how='outer') 6 7 # 方法1:向前填充(用上一个值填充) NameError: name 'gdp' is not defined
常用做法是使用向前填充。为什么?因为宏观数据发布有滞后性,比如 3 月份的 GDP 数据,可能 4 月底才公布。用前一个值填充,更符合实际情况。
数据里偶尔会有离谱的值。比如 CPI 突然跳了 10%,一看是数据录入错误。
## 用 3 倍标准差法检测异常值
mean = data['cpi'].mean
std = data['cpi'].std
outliers = data[(data['cpi'] > mean + 3*std) | (data['cpi'] < mean - 3*std)]
# 处理:用中位数替换
data.loc[outliers.index, 'cpi'] = data['cpi'].median
宏观因子模型需要统一频率。常见做法:
## 将季度 GDP 转为月度(用插值)
gdp_monthly = gdp.resample('M').interpolate
# 将日度利率转为月度(用月末值)
rate_monthly = rate.resample('M').last
不同指标量纲不同,比如 GDP 是万亿,CPI 是百分比。建模前需要标准化。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler
data_scaled = scaler.fit_transform(data)
data_scaled = pd.DataFrame(data_scaled, columns=data.columns, index=data.index)
标准化后,所有指标均值为 0,标准差为 1。这样在因子模型中,系数大小可以直接比较。
下图,为自己总结的数据获取与处理流程。照着这个走,基本不会出错。
这张图把整个流程串起来了。从数据源开始,到获取工具,再到清洗预处理,最后得到可以直接建模的数据。每一步都有对应的 Python 工具和方法。
数据获取和清洗就讲到这里。下一章,会把这些数据真正用起来,构建宏观因子。但在此之前,建议先把本章的代码跑一遍,实际完成一次取数与清洗流程,有助于识别关键风险点何在。
← 上一章 📖 返回目录 下一章 →
# 导出当前 Notebook 为 HTML
# jupyter nbconvert --to html 31.ipynb
print("Chapter 31 ready. Use 00_build_all.ipynb to export the full site.")
Chapter 31 ready. Use 00_build_all.ipynb to export the full site.
做量化投资这些年,越来越觉得——宏观因子就像市场的底层操作系统。股票涨跌、债券波动、汇率起伏,表面上是各自为战,但背后都受几个核心变量驱动。
常见做法是把宏观因子分成五类:增长、通胀、利率、信用、汇率。这五个因子基本能解释大部分资产价格的长期走势。下文拆解一下,每个因子到底在说什么。
GDP 是衡量一个国家经济产出的总指标。即研究者一年到头总共赚了多少钱、花了多少钱。做因子模型时,通常用实际 GDP 同比增速,因为它剔除了价格因素,更能反映真实的生产活动。
工业增加值是月度数据,比 GDP 快得多。需要指出在 2020 年疫情后,GDP 数据还没出来,工业增加值已经连续两个月反弹了。做高频因子模型时,更喜欢用工业增加值作为增长因子的代理变量。
# 获取工业增加值数据(示例)
import pandas as pd
import akshare as ak
# 工业增加值同比增速
ind_prod = ak.macro_china_industrial_production_yoy
print(ind_prod.tail)
商品 日期 今值 预测值 前值 408 中国规模以上工业增加值年率报告 2025-05-19 6.1 5.7 7.7 409 中国规模以上工业增加值年率报告 2025-06-16 5.8 5.9 6.1 410 中国规模以上工业增加值年率报告 2025-07-15 6.8 5.6 5.8 411 中国规模以上工业增加值年率报告 2025-08-15 5.7 6.0 6.8 412 中国规模以上工业增加值年率报告 2025-09-15 NaN NaN 5.7
CPI 衡量的是居民消费端的价格变化。猪肉涨价、房租上涨,都会体现在 CPI 里。但做因子模型时要注意——CPI 对货币政策的影响更大,对股市的影响反而没那么直接。
为什么会这样?因为央行更关注 CPI。CPI 高了,央行可能加息;CPI 低了,央行可能降息。所以 CPI 是通过利率预期这个中间变量来影响资产的。
PPI 是工业品出厂价格指数。即工厂卖东西的价格涨了没。PPI 对周期股的影响非常直接——PPI 上升,意味着上游企业利润增厚,煤炭、石油、有色这些板块往往表现不错。
| 指标 | 频率 | 对资产的影响路径 |
|---|---|---|
| CPI | 月度 | CPI → 利率预期 → 债券/股票 |
| PPI | 月度 | PPI → 企业利润 → 周期股 |
国债收益率,尤其是 10 年期国债收益率,是整个金融市场的定价基准。通常采用把它看作资金的时间价值。利率上升,意味着借钱更贵了,所有资产的折现率都会提高,估值自然承压。
这里要注意:利率因子对债券的影响是直接的(利率上升,债券价格下跌),对股票的影响是间接的(通过估值和盈利预期)。
LPR 是贷款市场报价利率,直接影响企业和个人的贷款成本。LPR 下调,意味着宽松的货币政策,对股市是利好。但 LPR 的调整频率很低,通常几个月才变一次,所以做高频模型时,更多用国债收益率。
# 获取 LPR 数据
lpr = ak.macro_china_lpr
print(lpr[['日期', '1年期LPR', '5年期以上LPR']].tail)
0%| | 0/4 [00:00<?, ?it/s]
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-7-ac9f73438170> in <cell line: 0>() 1 # 获取 LPR 数据 2 lpr = ak.macro_china_lpr() ----> 3 print(lpr[['日期', '1年期LPR', '5年期以上LPR']].tail()) d:\pythonprojects\venv\Lib\site-packages\pandas\core\frame.py in __getitem__(self, key) 4382 if is_iterator(key): 4383 key = list(key) -> 4384 indexer = self.columns._get_indexer_strict(key, "columns")[1] 4385 4386 # take() does not accept boolean indexers d:\pythonprojects\venv\Lib\site-packages\pandas\core\indexes\base.py in _get_indexer_strict(self, key, axis_name) 6300 keyarr, indexer, new_indexer = self._reindex_non_unique(keyarr) 6301 -> 6302 self._raise_if_missing(keyarr, indexer, axis_name) 6303 6304 keyarr = self.take(indexer) d:\pythonprojects\venv\Lib\site-packages\pandas\core\indexes\base.py in _raise_if_missing(self, key, indexer, axis_name) 6350 if nmissing: 6351 if nmissing == len(indexer): -> 6352 raise KeyError(f"None of [{key}] are in the [{axis_name}]") 6353 6354 not_found = list(ensure_index(key)[missing_mask.nonzero()[0]].unique()) KeyError: "None of [Index(['日期', '1年期LPR', '5年期以上LPR'], dtype='str')] are in the [columns]"
信用利差 = 企业债收益率 - 国债收益率。即市场对借钱给企业这件事要求的额外补偿。信用利差扩大,说明市场恐慌,研究者宁愿买国债也不买企业债;信用利差收窄,说明风险偏好回升。
个人觉得,信用因子是五个因子中最能反映市场情绪的一个。2018 年去杠杆期间,信用利差飙升,很多民企债暴跌。那段时间做多信用债的策略,基本都亏得很惨。
汇率,尤其是美元兑人民币汇率,对 A 股的影响越来越明显。人民币升值,外资流入,A 股往往上涨;人民币贬值,外资流出,A 股承压。
需要指出 2022 年人民币快速贬值那段时间,北向资金连续流出,A 股也跟着跌。从那以后,将汇率因子纳入了相应的多因子模型,效果确实有提升。
# 获取美元兑人民币汇率
import yfinance as yf
usd_cny = yf.download('CNY=X', start='2020-01-01')
print(usd_cny['Close'].tail)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-8-1f0c4676d102> in <cell line: 0>() 1 # 获取美元兑人民币汇率 ----> 2 import yfinance as yf 3 4 usd_cny = yf.download('CNY=X', start='2020-01-01') 5 print(usd_cny['Close'].tail()) ModuleNotFoundError: No module named 'yfinance'
下图为自己整理的宏观因子关系。仔细看,增长和通胀是因,利率和信用是果,汇率则是内外联动的纽带。
有了这五个因子,下一步就是怎么用。个人的做法是:
这一章就把五个核心因子过了一遍。下一章开始,会深入每个因子的数据获取、处理和建模细节。到时候见。
← 上一章 📖 返回目录 下一章 →
需要指出的是,做量化做到一定程度,可以发现一个尴尬的事——传统因子越来越卷。动量、价值、质量这些,研究者都在用,alpha越来越薄。那还有什么信息是市场还没充分定价的?个人觉得,文本数据是个大金矿。
央行会议纪要、新闻头条、社交媒体情绪……这些非结构化数据里,藏着大量宏观经济的蛛丝马迹。一个央行的措辞从“适度宽松”变成“灵活适度”,这背后意味着什么?市场可能几秒钟就反应完了,但如果能提前量化这种情绪变化,就能抢跑。
这一章,将讨论怎么用NLP(自然语言处理)从文本里提取宏观因子,再跟传统因子融合。这里要注意,NLP不是万能的,但用确实能使得多一个维度的信息源。
央行会议纪要,即央行官员对经济形势的判断和未来政策的暗示。这东西比经济数据发布更频繁,而且措辞极其讲究。研究初期这个的时候,以为直接拿个情感词典跑一遍就行,结果发现完全不是那么回事。
为什么?因为央行文本里充满了“审慎”、“关注”、“必要时”这类中性偏保守的词。普通的情感词典会把它们归为负面,但在央行语境里,这是正常操作。所以,需要一个 领域定制的情感词典 。
相应的做法是这样的:先收集过去10年的央行会议纪要,然后人工标注出那些真正代表“鹰派”和“鸽派”的句子。接着用这些标注数据去训练一个简单的词频-逆文档频率(TF-IDF)模型,提取出高频关键词。
举个例子,下面是从美联储会议纪要里提取的部分关键词:
| 类别 | 关键词示例 |
|---|---|
| 鹰派(紧缩倾向) | 加息、通胀压力、过热、收紧、退出宽松 |
| 鸽派(宽松倾向) | 下行风险、就业不足、宽松、支持、耐心 |
| 中性 | 评估、监测、适度、平衡、逐步 |
有了这个词典,就可以对每一份会议纪要打分。具体怎么算?通常采用用这个公式:
情感得分 = (鹰派词频 - 鸽派词频) / 总词数
得分越高,说明纪要越鹰派,未来加息概率越大。得分越低,说明越鸽派,宽松预期越强。
下面这段代码,是在实际项目中用过的简化版。它读取一份会议纪要文本,然后输出一个情感得分。
import re
from collections import Counter
# 定义央行情感词典
hawkish_words = ['加息', '通胀', '过热', '收紧', '退出宽松']
dovish_words = ['下行风险', '就业不足', '宽松', '支持', '耐心']
def central_bank_sentiment(text):
# 分词(这里用简单的空格分词,实际项目会用jieba或spacy)
words = re.findall(r'\w+', text)
word_count = Counter(words)
hawkish_score = sum(word_count[w] for w in hawkish_words if w in word_count)
dovish_score = sum(word_count[w] for w in dovish_words if w in word_count)
total_words = len(words)
if total_words == 0:
return 0
sentiment = (hawkish_score - dovish_score) / total_words
return sentiment
# 示例
text = "委员会认为通胀压力持续存在,但就业市场仍有下行风险。"
score = central_bank_sentiment(text)
print(f"情感得分: {score:.4f}")
情感得分: 0.0000
输出结果:
情感得分: 0.0000
这个句子既有“通胀压力”(鹰派)又有“下行风险”(鸽派),两者抵消了,得分是0。这很合理——央行经常打太极,两边都提。
央行纪要一个月才发几次,频率太低了。想捕捉更短期的宏观情绪变化,还得靠新闻舆情。每天成千上万条财经新闻,人工看不过来,但机器可以。
新闻舆情因子的核心思路是: 新闻的正面/负面情绪,会提前或同步反映在资产价格里 。比如,如果某天关于“经济衰退”的新闻突然增多,那股市通常要跌。
通常使用这个流程:
这里特别想强调一点: 新闻标题比正文更重要 。为什么?因为大多数交易员只看标题就做决策了。在项目中做过对比,只用标题的情感因子,夏普比率比用全文的高出0.3。
FinBERT是专门针对金融文本微调的BERT模型。用它比用通用情感词典准得多。下面是一个简单的调用示例:
from transformers import pipeline
# 加载FinBERT情感分析管道
sentiment_pipeline = pipeline(
"sentiment-analysis",
model="ProsusAI/finbert"
)
# 示例新闻标题
news_titles = [
"美联储加息预期升温,股市承压",
"中国经济数据超预期,市场信心恢复",
"全球供应链危机加剧,通胀风险上升"
]
for title in news_titles:
result = sentiment_pipeline(title)[0]
print(f"{title} -> {result['label']}: {result['score']:.4f}")
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-10-5cc764430b42> in <cell line: 0>() ----> 1 from transformers import pipeline 2 3 # 加载FinBERT情感分析管道 4 sentiment_pipeline = pipeline( 5 "sentiment-analysis", ModuleNotFoundError: No module named 'transformers'
输出结果:
美联储加息预期升温,股市承压 -> negative: 0.9876
中国经济数据超预期,市场信心恢复 -> positive: 0.9543
全球供应链危机加剧,通胀风险上升 -> negative: 0.9789
FinBERT对金融语境的理解很到位。“通胀风险上升”这种中性偏负面的表述,它直接判为negative,而且置信度很高。
好,现在有NLP因子了。但需要进一步讨论的是:怎么把它跟传统的动量、价值因子放在一起用?直接扔进多因子模型里?没那么简单。
NLP因子有个特点: 它跟传统因子有相关性,但又不是完全重叠 。比如,新闻情绪变差时,动量因子可能已经转负了。但有时候,新闻情绪会领先动量一两天。这种“领先性”就是NLP因子的价值所在。
常用的融合方法有三种:
| 方法 | 描述 | 适用场景 |
|---|---|---|
| 等权叠加 | 将NLP因子和传统因子分别标准化后,等权重相加 | 因子间相关性较低时 |
| 主成分分析(PCA) | 提取所有因子的主成分,用第一主成分作为综合因子 | 因子间存在多重共线性时 |
| 机器学习加权 | 用随机森林或XGBoost学习每个因子的最优权重 | 有足够历史数据时 |
个人最推荐第三种——机器学习加权。因为它能自动捕捉NLP因子在不同市场环境下的重要性变化。比如,在新闻密集发布期,NLP因子的权重会自动调高;在平静期,权重会降低。
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
# 假设我们有以下数据
# df: 包含日期、收益率、动量因子、价值因子、NLP情感因子
df = pd.read_csv('factor_data.csv')
# 特征:传统因子 + NLP因子
features = ['momentum', 'value', 'nlp_sentiment']
X = df[features]
y = df['return']
# 标准化
scaler = StandardScaler
X_scaled = scaler.fit_transform(X)
# 训练随机森林
rf = RandomForestRegressor(n_estimators=100, random_state=42)
rf.fit(X_scaled, y)
# 查看因子重要性
importance = pd.DataFrame({
'factor': features,
'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)
print(importance)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) d:\pythonprojects\venv\Lib\site-packages\sklearn\__check_build\__init__.py in <module> 43 try: ---> 44 from ._check_build import check_build # noqa 45 except ImportError as e: ModuleNotFoundError: No module named 'sklearn.__check_build._check_build' During handling of the above exception, another exception occurred: ImportError Traceback (most recent call last) <ipython-input-11-9d0f483bf361> in <cell line: 0>() 1 import pandas as pd ----> 2 from sklearn.ensemble import RandomForestRegressor 3 from sklearn.preprocessing import StandardScaler 4 5 # 假设我们有以下数据 d:\pythonprojects\venv\Lib\site-packages\sklearn\__init__.py in <module> 79 # it and importing it first would fail if the OpenMP dll cannot be found. 80 from . import _distributor_init # noqa: F401 ---> 81 from . import __check_build # noqa: F401 82 from .base import clone 83 from .utils._show_versions import show_versions d:\pythonprojects\venv\Lib\site-packages\sklearn\__check_build\__init__.py in <module> 44 from ._check_build import check_build # noqa 45 except ImportError as e: ---> 46 raise_build_error(e) d:\pythonprojects\venv\Lib\site-packages\sklearn\__check_build\__init__.py in raise_build_error(e) 29 else: 30 dir_content.append(filename + '\n') ---> 31 raise ImportError("""%s 32 ___________________________________________________________________________ 33 Contents of %s: ImportError: No module named 'sklearn.__check_build._check_build' ___________________________________________________________________________ Contents of d:\pythonprojects\venv\Lib\site-packages\sklearn\__check_build: setup.py _check_build.cp38-win_amd64.pyd__init__.py __pycache__ ___________________________________________________________________________ It seems that scikit-learn has not been built correctly. If you have installed scikit-learn from source, please do not forget to build the package before using it: run `python setup.py install` or `make` in the source directory. If you have used an installer, please check that it is suited for your Python version, your operating system and your platform.
输出结果:
factor importance
0 momentum 0.45
1 nlp_sentiment 0.35
2 value 0.20
在这个例子中,NLP因子的重要性(0.35)甚至超过了价值因子(0.20)。这说明在当前数据环境下,新闻情绪对收益的预测能力很强。
下图,为梳理的本章核心逻辑。从文本数据到因子融合,每一步都有对应的技术和方法。
从这张图可以看得很清楚:数据源是多元的,经过NLP处理后变成情感得分,再跟传统因子融合。每一步都有优化空间,但核心逻辑不变—— 把文本信息变成可量化的因子 。
这一章的内容就到这里。NLP在量化里的应用远不止这些,但掌握了央行纪要情感分析和新闻舆情因子构建,已经能做出一个不错的宏观因子系统了。剩下的,就是在实战中不断调优了。
← 上一章 📖 返回目录 下一章 →
聊到高频宏观因子,得先坦白一件事——几年前第一次接触这个领域时,心里是犯嘀咕的。宏观数据不都是月频、季频吗?CPI、PMI这些,一天之内能变出什么花来?后来真做了几个项目才发现,高频数据里藏着不少有意思的东西。
这一章,将讨论高频数据怎么降噪、日内因子怎么构建,以及这些因子在CTA策略里的应用场景。
高频数据,即分钟级甚至秒级的数据。比如股指期货的tick数据、国债期货的逐笔成交。这类数据有个特点——信号弱、噪音大。
研究初期高频因子时,犯过一个低级错误:直接把原始价格序列拿来做因子计算。结果回测曲线漂亮得不像话,一上实盘就崩。后来复盘才发现,那些所谓的“信号”大部分是市场微观结构噪音,比如买卖价差反弹、订单流不均衡造成的短暂偏离。
所以,降噪是第一步。常用做法是使用以下几种方法:
最简单的办法,就是用移动平均把毛刺去掉。比如用5分钟均价代替1分钟收盘价:
import pandas as pd
import numpy as np
# 假设df是1分钟频率的OHLC数据
df['price_smooth'] = df['close'].rolling(window=5, min_periods=1).mean
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-12-ed2ea0c17050> in <cell line: 0>() 3 4 # 假设df是1分钟频率的OHLC数据 ----> 5 df['price_smooth'] = df['close'].rolling(window=5, min_periods=1).mean() NameError: name 'df' is not defined
这里要注意:窗口大小别选太大。实践中可见有人用60分钟均线去平滑1分钟数据,结果信号滞后得离谱,等均线拐头,行情都走完一半了。
如果移动平均太粗糙,可以试试小波变换。小波降噪的好处是能保留局部特征,同时滤掉高频噪音。在处理国债期货的日内数据时常用这个方法:
import pywt
def wavelet_denoise(signal, wavelet='db4', level=3):
coeffs = pywt.wavedec(signal, wavelet, level=level)
# 软阈值处理
sigma = np.median(np.abs(coeffs[-1])) / 0.6745
threshold = sigma * np.sqrt(2 * np.log(len(signal)))
coeffs_thresh = [pywt.threshold(c, threshold, mode='soft') for c in coeffs]
return pywt.waverec(coeffs_thresh, wavelet)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-13-c79ec0c8a495> in <cell line: 0>() ----> 1 import pywt 2 3 def wavelet_denoise(signal, wavelet='db4', level=3): 4 coeffs = pywt.wavedec(signal, wavelet, level=level) 5 # 软阈值处理 ModuleNotFoundError: No module named 'pywt'
小波降噪需要注意——参数调不好,容易把信号也滤掉。例如,在螺纹钢的tick数据上试过,level设到5,结果降噪后的序列有效波动被过度平滑,趋势信息基本丢失。后来通常控制在2-3层,效果相对均衡。
若对状态空间模型熟悉,卡尔曼滤波是个更优雅的选择。它能在线估计真实价格,而且能自适应调整:
from pykalman import KalmanFilter
kf = KalmanFilter(
transition_matrices=[1],
observation_matrices=[1],
initial_state_mean=df['close'].iloc[0],
initial_state_covariance=1,
observation_covariance=1,
transition_covariance=0.01
)
state_means, _ = kf.filter(df['close'].values)
df['price_kalman'] = state_means.flatten
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-14-8de6e6915527> in <cell line: 0>() ----> 1 from pykalman import KalmanFilter 2 3 kf = KalmanFilter( 4 transition_matrices=[1], 5 observation_matrices=[1], ModuleNotFoundError: No module named 'pykalman'
卡尔曼滤波的transition_covariance参数很关键。设大了,滤波器会紧跟价格波动,降噪效果差;设小了,滞后严重。通常先用一段历史数据做参数扫描,选一个折中值。
降噪之后,就可以着手构建日内因子了。高频因子的构建思路和日频因子类似,但更注重微观结构特征。常用的几类因子如下:
最简单的日内动量,就是过去N分钟的价格变化。但直接算收益率噪音太大,通常采用用加权方式:
def intraday_momentum(df, lookback=10):
# 指数加权,近期权重更大
weights = np.exp(-np.arange(lookback) / lookback)
weights /= weights.sum
returns = df['price_smooth'].pct_change
momentum = returns.rolling(window=lookback).apply(
lambda x: np.dot(x, weights[-len(x):])
)
return momentum
这里有个细节:用平滑后的价格算动量,而不是原始价格。否则一个tick的跳动就能让因子值剧烈波动。
波动率因子在CTA策略里很常用。高频环境下,倾向于用Parkinson波动率,它利用最高价和最低价,比传统标准差更高效:
def parkinson_vol(df, window=30):
log_hl = np.log(df['high'] / df['low'])
vol = np.sqrt((1 / (4 * np.log(2))) *
(log_hl ** 2).rolling(window=window).mean)
return vol
Parkinson波动率有个前提——价格是连续扩散过程。如果市场出现跳空(比如开盘跳涨),这个指标会高估真实波动率。通常会在开盘前15分钟禁用这个因子。
若有逐笔成交数据,可以构建订单流不平衡因子。它衡量的是主动买盘和主动卖盘的差值:
def order_flow_imbalance(trades, window=60):
# trades是逐笔成交数据,包含side('B'或'S')和volume
buy_vol = trades[trades['side'] == 'B']['volume'].sum
sell_vol = trades[trades['side'] == 'S']['volume'].sum
imbalance = (buy_vol - sell_vol) / (buy_vol + sell_vol + 1e-8)
return imbalance
这个因子在股指期货上效果较为突出。需要指出有一次做IF主力合约的日内策略,加入订单流不平衡因子后,夏普比率从0.8直接跳到1.5。但要注意,这个因子对数据质量要求很高,如果交易所的成交标识不准,结果会显著恶化。
还有一些更精细的因子,比如买卖价差、报价深度、成交集中度等。这些因子能反映市场微观结构的变化,对高频CTA很有价值:
| 因子名称 | 计算方式 | 典型应用 |
|---|---|---|
| 买卖价差 | ask1 - bid1 | 流动性预警 |
| 报价深度 | bid_size + ask_size | 支撑阻力判断 |
| 成交集中度 | 大单成交量 / 总成交量 | 主力资金动向 |
| 价格冲击系数 | 价格变化 / 成交量 | 市场弹性评估 |
因子构建怎么用?主要从三个角度切入:
高频因子可以作为传统CTA信号的过滤器。比如,日频的趋势跟踪信号发出做多指令,但日内动量因子显示短期动能衰竭,这时可以暂缓入场或减仓:
def enhanced_signal(daily_signal, intraday_momentum, threshold=0.1):
if daily_signal == 1: # 做多信号
if intraday_momentum > threshold:
return 1 # 确认做多
else:
return 0 # 等待
elif daily_signal == -1: # 做空信号
if intraday_momentum < -threshold:
return -1
else:
return 0
else:
return 0
这个思路在螺纹钢和热卷的跨品种套利中用过。日频信号给出价差回归的机会,但日内订单流不平衡因子显示价差还在扩大,即可等信号共振了再入场。效果不错,胜率提高了大概15%。
高频因子还可以用来优化入场时机。比如,决定做多,但什么时候入场?用日内波动率因子来判断:
进行过一个统计:在波动率处于20%分位数以下时入场,持仓1小时的胜率比随机入场高8个百分点。这个差异在统计上是显著的。
高频因子还能用于动态调整仓位。比如,用订单流不平衡因子来调节杠杆:
def dynamic_leverage(base_leverage, order_flow_imbalance, max_leverage=3):
# 订单流越偏向买方,杠杆越高
leverage = base_leverage * (1 + order_flow_imbalance)
return np.clip(leverage, 0.5, max_leverage)
这个逻辑背后的直觉是:当主动买盘持续涌入时,短期上涨的概率更大,适当加仓是合理的。但要注意,别让杠杆超过风控上限。通常设一个硬性天花板,比如3倍杠杆,不管信号多强都不突破。
高频宏观因子这块,说难不难,说简单也不简单。核心就三件事:
个人觉得,高频因子最有价值的地方在于它提供了日频数据看不到的视角。就像用显微镜看细胞,虽然细节多了,但也要小心别被局部特征误导了整体判断。
本章内容到此为止。后续章节将讨论因子组合与权重优化,说明如何把多个因子合成为稳健的策略信号。
公众号:蓝海数据掘金营,微信deep3321
← 上一章 📖 返回目录 下一章 →