尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从拓扑数据分析到行情结构因子:持续同调与持久图实战

从拓扑数据分析到行情结构因子:持续同调与持久图实战 1. 从拓扑视角重新理解金融行情做量化研究的朋友一定经历过这样的阶段技术指标用了一大堆K 线形态也学了不少但真正落到因子库里的东西总觉得缺了点“结构感”。我们平时看的均线、MACD、布林带本质上都在做同一件事——把价格序列映射到某个数值区间然后用阈值去判断状态。这种方式当然有效但它有一个隐性问题它丢失了行情数据在拓扑层面的形状信息。什么叫拓扑层面的形状信息举个直观例子。同样一段 60 日均线如果行情是“窄幅箱体震荡后突然拉升”和“宽幅V型反转后缓慢爬升”两种行情在均线数值上可能非常接近但它们的数据形态完全不同。一个像收缩的圆环一个像拉长的曲线在拓扑学里它们的“洞”和“连通分量”结构是不同的。拓扑数据分析Topological Data AnalysisTDA就是专门用来捕捉这些形状结构的数学工具。它不关注价格具体涨到多少、跌到多少而是关注数据的连通性、空洞、凹陷、聚类结构在时间尺度上的变化。为什么这个思路对行情研究有价值因为金融市场的结构变化往往先于价格变化。趋势启动前筹码结构从分散走向密集趋势衰竭时多空力量从均衡走向失衡。这些变化在拓扑特征里会有可量化的信号。本文围绕拓扑数据分析TDA的行情结构因子挖掘展开适合有机器学习或量化基础、想拓展因子来源的开发者阅读。读完你能掌握TDA 的核心概念单纯复形、持续同调、持久图。如何用 Python 对行情序列做拓扑特征提取。如何把拓扑量化为可用的结构因子。如何对因子做初步的收益分析与组合优化。在实际工程中会遇到哪些坑以及怎么规避。我们不做数学上的严格公理化而是以“能落地、能写代码、能跑出结果”为第一目标。2. 拓扑数据分析的基础概念2.1 点云数据与距离矩阵TDA 的输入通常是点云数据point cloud也就是一堆高维空间中的点。放到金融场景中我们可以把一段时间的行情窗口映射成点云每个点代表一个交易日。特征维度可以是最高价、最低价、成交量、持仓量、波动率。点与点之间的距离反映不同时间截面上的市场状态相似度。当我们把每个交易日的多维特征看作高维空间中的一个点连续多个交易日就形成了一条高维曲线。这条曲线在拓扑学上具有形状特征。距离度量可以选择欧氏距离、曼哈顿距离也可以根据业务场景自定义。例如如果你想突出“波动结构”可以只用收益率和振幅如果你想突出“量价结构”则加入成交量归一化值。2.2 单纯复形与连通性有了点云数据之后我们需要一种方式描述这些点之间的“连接关系”。单纯复形Simplicial Complex就是这样一个工具。单纯复形的思想很简单0 维单纯形是点。1 维单纯形是边当两个点距离小于阈值时连成一条边。2 维单纯形是三角形当三个点两两距离都小于阈值时形成一个三角形面。更高维度可以类推。如果我们用一个距离阈值 r 逐步增大点云中的连边会逐渐增多单纯复形不断变化。这个变化过程记录下来的拓扑特征就是后续因子的核心来源。2.3 持续同调与持久图持续同调Persistent Homology是 TDA 中最重要的计算工具。它的核心思想是随着距离阈值从 0 增大到无穷大单纯复形的拓扑特征连通分量、空洞、高维空腔会不断出现和消失。我们要记录每个拓扑特征“出生”和“死亡”的阈值。一个拓扑特征在某个阈值范围 r_birth 出现在 r_death 消失那么它的“生命周期”就是 r_death - r_birth。生命周期越长说明这个拓扑结构越稳定。把每个拓扑特征的 (出生阈值, 死亡阈值) 绘制成图就得到持久图Persistence Diagram。持久图有两种理解方式出生、死亡坐标越远离对角线说明拓扑特征越显著。点越靠近对角线说明该特征生命周期短通常是噪声。我们可以计算 H0连通分量、H1一维空洞、H2二维空洞等不同维度的持续同调特征。在行情数据中H0 描述点云的聚类结构变化H1 描述行情数据是否形成类似环形或空洞的结构这类结构往往对应特定的盘整与突破模式。3. 为什么不直接用传统技术指标看到这里你可能会有疑问传统技术指标已经够用为什么要绕一大圈去算拓扑特征这个问题非常关键。拓扑因子的价值不在于“取代传统技术指标”而在于“补充传统指标看不到的结构维度”。传统技术指标有几个结构性问题第一指标存在滞后性。均线、MACD 都是基于历史窗口的线性计算行情结构发生变化时指标通常要等几个周期才能反映。第二阈值设定缺乏自适应性。很多指标有固定参数比如 20 日均线、60 日均线但市场行情在不同阶段的最优窗口长度差异很大。第三多特征组合的信息压缩过度。我们往往把多个指标加权求和或做布尔组合但这种方式会丢失特征之间的交互结构。而 TDA 的思路完全不同。它不直接预测价格而是描述数据在高维空间中的形状。当行情从趋势状态切换到震荡状态时拓扑特征会发生变化当多空力量从均衡转向失衡时H1 或 H0 特征也会出现迁移。这些变化本质上反映的是市场内部结构比单一价格指标更接近行情本质。需要说明的是拓扑因子不是万能的。它解决的是“结构识别”问题而不是“方向预测”问题。因此在实际因子挖掘中拓扑因子更适合做状态识别、风控过滤、叠加信号而不是单独作为择时信号。4. 环境准备与依赖安装4.1 运行环境说明本文示例以 Python 3.9 以上版本为主操作系统不限Windows、macOS、Linux 均可。核心库包括numpy数值计算。pandas数据处理。ripser持续同调计算。persim持久图可视化与距离计算。matplotlib绘图。scikit-learn可选用于标准化和聚类。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你的环境中已经安装了部分库可以跳过对应步骤。4.2 安装核心依赖建议使用虚拟环境避免依赖冲突python -m venv tda_env source tda_env/bin/activate # Windows 下使用 tda_env\Scripts\activate pip install numpy pandas matplotlib scikit-learn pip install ripser persim如果你的网络环境安装较慢可以使用国内镜像源pip install ripser persim -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后验证导入是否正常import ripser import persim print(ripser version:, ripser.__version__ if hasattr(ripser, __version__) else unknown) print(persim imported successfully)如果导入报错通常是依赖的 C 扩展库没有编译成功。建议先升级 pip 和 setuptoolspip install --upgrade pip setuptools wheel4.3 数据准备说明本文使用示例数据演示流程。你可以用 tushare、akshare 拉取真实行情也可以直接用以下代码生成一段模拟行情来跑通流程。模拟数据的好处是你知道真实结构便于验证拓扑特征是否正确。5. 行情点云构造与拓扑特征提取5.1 将行情窗口转换为点云把行情数据转换为点云是实现 TDA 因子的第一步。这里给出一种常用做法滑窗 多维特征映射。假设我们有某个品种的日线数据包含 open、high、low、close、volume 五列。我们取过去 N 个交易日作为一个窗口窗口内每个交易日映射为一个点特征 1当日收益率。特征 2当日振幅high-low/ close。特征 3当日成交量相对窗口均值的偏离。特征 4当日收盘价相对窗口最高价的偏离。这样每一个窗口就得到一个形状为 (N, 4) 的点云矩阵。窗口不断滑动每个窗口都能提取一组拓扑特征最终形成拓扑因子时序。我们先用模拟数据演示完整流程import numpy as np import pandas as pd # 生成模拟行情数据 np.random.seed(42) n 1500 returns np.random.normal(0, 0.02, n) price 100 * np.exp(np.cumsum(returns)) # 模拟高低价和成交量 high price * (1 np.abs(np.random.normal(0, 0.01, n))) low price * (1 - np.abs(np.random.normal(0, 0.01, n))) volume np.random.uniform(0.8, 1.2, n) * 1e6 df pd.DataFrame({ close: price, high: high, low: low, volume: volume, }) df.head()这段代码生成了 1500 个交易日的模拟行情。这里没有任何趋势结构纯随机游走目的是先跑通流程后面再叠加真实数据。5.2 窗口内点云特征构造函数下面编写一个函数将行情 DataFrame 转成滑窗点云def build_point_cloud(df, window30): 将行情数据转换为滑窗点云列表 参数 df: DataFrame, 包含 close, high, low, volume 列 window: int, 窗口长度 返回 point_clouds: list, 每个元素是一个 (window, feature_dim) 的 ndarray close df[close].values high df[high].values low df[low].values volume df[volume].values point_clouds [] for i in range(len(df) - window 1): window_close close[i:iwindow] window_high high[i:iwindow] window_low low[i:iwindow] window_volume volume[i:iwindow] # 当日收益率 ret np.diff(window_close) / window_close[:-1] # 当日振幅 amp (window_high[1:] - window_low[1:]) / window_close[1:] # 成交量相对窗口均值的偏离 vol_dev (window_volume[1:] - window_volume[1:].mean()) / (window_volume[1:].mean() 1e-10) # 收盘价相对窗口最高价的偏离 close_dev (window_close[1:] - window_close[1:].max()) / (window_close[1:].max() 1e-10) # 组合成点云 point_cloud np.column_stack([ret, amp, vol_dev, close_dev]) point_clouds.append(point_cloud) return point_clouds这里需要注意点云矩阵的行数等于 window-1因为收益率计算需要相邻两天做差。窗口滑动的步长默认是 1如果你希望降低计算量可以把步长调大。5.3 标准化点云数据在计算持续同调之前建议对每个窗口的点云做标准化。原因在于持续同调基于距离矩阵计算不同特征的量纲差异会影响距离度量。from sklearn.preprocessing import StandardScaler def normalize_point_clouds(point_clouds): 对每个窗口的点云做标准化 normalized [] for pc in point_clouds: scaler StandardScaler() pc_norm scaler.fit_transform(pc) normalized.append(pc_norm) return normalized标准化后再计算距离矩阵拓扑特征反映的是“结构形态”而不是“绝对数值”。这一点对跨品种、跨时间段的因子迁移非常重要。5.4 使用 ripser 计算持续同调接下来是核心计算部分。我们使用 ripser 库来计算每个窗口的持续同调特征。from ripser import ripser from persim import plot_diagrams def compute_persistence(point_clouds, max_dim2): 计算每个点云的持续同调 参数 point_clouds: list, 点云列表 max_dim: int, 最大同调维度 返回 diagrams_list: list, 每个元素是 ripser 输出的持久图结果 diagrams_list [] for pc in point_clouds: diagrams ripser(pc, maxdimmax_dim)[dgms] diagrams_list.append(diagrams) return diagrams_list这里maxdim2表示我们计算 H0、H1、H2 三个维度的持续同调。H0 对应连通分量H1 对应一维空洞H2 对应二维空腔。维度越高计算耗时越长噪声也越大。对日线行情来说H0 和 H1 通常已经能提供足够信息H2 需要样本量较大才稳定。5.5 可视化单个窗口的持久图为了直观理解我们取第一个窗口的持久图看一下import matplotlib.pyplot as plt # 构造点云并计算 point_clouds build_point_cloud(df, window30) point_clouds normalize_point_clouds(point_clouds) diagrams_list compute_persistence(point_clouds, max_dim2) # 绘制第一个窗口的持久图 plt.figure(figsize(8, 6)) plot_diagrams(diagrams_list[0], showTrue)在持久图中横轴是出生阈值纵轴是死亡阈值。点越远离对角线代表该拓扑特征越持久。我们关注的主要是H0 中横坐标较大的点连通分量在较大尺度上才合并说明点云分布分散。H1 中远离对角线的点存在显著的一维空洞结构。6. 从持久图到可计算的行情结构因子持久图本身不能直接作为因子输入我们需要把拓扑特征压缩成标量形成因子序列。6.1 常用拓扑因子定义这里介绍几种常用的拓扑压缩指标它们计算简单且具有明确的行情含义。1总持久性Total Persistence对某一维度的所有拓扑特征将它们的持久性死亡阈值 - 出生阈值求和。这个指标反映该维度的拓扑复杂度。def total_persistence(diagram): 计算总持久性 if len(diagram) 0: return 0.0 persistence diagram[:, 1] - diagram[:, 0] return np.sum(persistence)可以分别计算 H0、H1 的 total persistence。H1 的总持久性越高说明行情点云中的空洞结构越明显这通常对应某种“箱体环绕”或“往复震荡”状态。2最大持久性Max Persistence取所有拓扑特征中最大生命周期。它反映最显著的形状结构是否存在。def max_persistence(diagram): 计算最大持久性 if len(diagram) 0: return 0.0 persistence diagram[:, 1] - diagram[:, 0] return np.max(persistence)当 H1 的最大持久性显著上升时说明行情中出现了一个非常稳定的环形结构意味着价格在某个区间内反复穿越但没有有效突破。3持久图质心Persistence Centroid将所有拓扑特征点按持久性加权计算出生、死亡的一阶矩。这个指标描述拓扑结构在持久图上的位置偏移。def persistence_centroid(diagram): 计算持久图质心 if len(diagram) 0: return np.array([0.0, 0.0]) persistence diagram[:, 1] - diagram[:, 0] total np.sum(persistence) if total 0: return np.array([0.0, 0.0]) centroid_x np.sum(diagram[:, 0] * persistence) / total centroid_y np.sum(diagram[:, 1] * persistence) / total return np.array([centroid_x, centroid_y])质心偏向右上方说明显著拓扑特征的出生、死亡阈值均较高即点云结构在大尺度上仍然保持。4持久熵Persistence Entropy持久熵衡量拓扑特征生命周期的分布均匀程度。它借鉴了信息熵的思想如果所有拓扑特征都近似相同生命周期熵值大如果只有一个显著特征熵值小。def persistence_entropy(diagram): 计算持久熵 if len(diagram) 0: return 0.0 persistence diagram[:, 1] - diagram[:, 0] total np.sum(persistence) if total 0: return 0.0 probs persistence / total # 避免 log(0) probs probs[probs 0] return -np.sum(probs * np.log(probs))持久熵结合最大持久性可以区分“单一强结构”和“多结构并存”的行情状态。6.2 逐窗口计算因子序列下面把所有压缩指标封装成一个函数对每个窗口输出一组因子值def compute_tda_features(diagrams_list, max_dim2): 将持久图列表转换为因子序列 参数 diagrams_list: list, 每个元素是 ripser 输出 max_dim: int 返回 features: DataFrame, 每行对应一个窗口 rows [] for diagrams in diagrams_list: row {} for dim in range(max_dim 1): dgm diagrams[dim] row[ftotal_pers_H{dim}] total_persistence(dgm) row[fmax_pers_H{dim}] max_persistence(dgm) row[fentropy_H{dim}] persistence_entropy(dgm) centroid persistence_centroid(dgm) row[fcentroid_x_H{dim}] centroid[0] row[fcentroid_y_H{dim}] centroid[1] # 额外增加 H0 点数与 H1 点数之比反映聚类复杂程度 row[num_H0] len(diagrams[0]) row[num_H1] len(diagrams[1]) if len(diagrams[0]) 0: row[ratio_H1_H0] len(diagrams[1]) / len(diagrams[0]) else: row[ratio_H1_H0] 0.0 rows.append(row) return pd.DataFrame(rows)最终生成的因子 DataFrame每一行对应一个窗口每一列对应一种拓扑压缩特征。6.3 将因子对齐到日期由于滑窗计算因子序列的起始日期会比原始行情晚 window 个交易日。我们需要做日期对齐# 假设原始 DataFrame 有日期索引 # df.index 是 DatetimeIndex if isinstance(df.index, pd.DatetimeIndex): tda_dates df.index[window - 1:] # 每个窗口取窗口最后一个交易日作为日期 else: tda_dates df.index[window - 1:] tda_features.index tda_dates[:len(tda_features)]这样每个拓扑因子都有对应的日期标记方便后续与收益率、其他因子做对齐分析。7. 因子有效性初步检验7.1 构建标签未来 N 日收益率因子挖掘的目标是找到对未来收益有预测能力的变量。我们以未来 5 日收益率为标签做一个简单分析# 计算未来 5 日收益 future_return df[close].pct_change(5).shift(-5) # 合并到 tda_features analysis_df tda_features.copy() analysis_df[future_return] future_return.reindex(tda_features.index) analysis_df analysis_df.dropna()这里需要注意shift(-5)的含义避免使用未来数据标签必须严格滞后于因子值。也就是说t 日的因子值对应的是 t5 日的收益率。7.2 分位数分组收益分析最常用的因子检验方法是分组回测。将因子按大小分成 5 组观察各组的未来平均收益def factor_group_analysis(analysis_df, factor_col, quantile5): 按因子分位数分组计算未来收益均值 参数 analysis_df: DataFrame, 包含因子和未来收益 factor_col: str, 因子列名 quantile: int, 分组数 analysis_df analysis_df.copy() analysis_df[group] pd.qcut(analysis_df[factor_col], quantile, labelsFalse, duplicatesdrop) grouped analysis_df.groupby(group)[future_return].mean() return grouped # 示例H1 总持久性分组 factor total_pers_H1 group_result factor_group_analysis(analysis_df, factor) print(group_result)如果分组结果呈现出单调性比如因子从小到大未来收益逐渐上升或下降说明该因子存在一定预测能力。如果分组收益呈现“U型”或倒“U型”说明因子更适合做状态过滤而不是线性预测因子。7.3 因子 IC 分析分组分析直观但有噪声。ICInformation Coefficient分析是更常用的连续性指标。IC 定义为因子值与未来收益的 Spearman 秩相关系数。from scipy.stats import spearmanr def compute_ic(analysis_df, factor_col): 计算因子的 Spearman IC df_clean analysis_df[[factor_col, future_return]].dropna() ic, p_value spearmanr(df_clean[factor_col], df_clean[future_return]) return ic, p_value ic_H1, p_H1 compute_ic(analysis_df, total_pers_H1) print(fH1 total persistence IC: {ic_H1:.4f}, p-value: {p_H1:.4f})IC 的绝对值越接近 1预测能力越强越接近 0说明线性相关性越弱。需要注意的是IC 低不代表因子无效可能因子是非线性的或者只在特定市场状态下有效。7.4 因子相关性矩阵多个拓扑因子之间可能存在高度共线性尤其 H0 和 H1 的指标往往同时变化。在组合使用前需要检查相关性corr_matrix analysis_df[[total_pers_H0, total_pers_H1, max_pers_H0, max_pers_H1, entropy_H0, entropy_H1]].corr() print(corr_matrix)如果两个因子相关系数超过 0.8建议只保留其中一个或者对它们做正交化处理。否则后续做多因子回归时会出现多重共线性问题。8. 一个完整的行情结构因子挖掘流程把前面的功能拼接起来就可以形成一个端到端的因子挖掘流程。下面给出完整示例脚本。8.1 完整代码示例import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from ripser import ripser from scipy.stats import spearmanr # ---------------- 1. 数据准备 ---------------- np.random.seed(1) n 2000 returns np.random.normal(0, 0.015, n) # 加入分段趋势 for i in range(400, 900): returns[i] 0.001 for i in range(900, 1400): returns[i] - 0.0008 price 100 * np.exp(np.cumsum(returns)) high price * (1 np.abs(np.random.normal(0, 0.008, n))) low price * (1 - np.abs(np.random.normal(0, 0.008, n))) volume np.random.uniform(0.8, 1.2, n) * 1e6 dates pd.date_range(2018-01-01, periodsn, freqD) df pd.DataFrame({ close: price, high: high, low: low, volume: volume, }, indexdates) # ---------------- 2. 构建点云 ---------------- window 30 def build_point_cloud(df, window30): close df[close].values high df[high].values low df[low].values volume df[volume].values point_clouds [] for i in range(len(df) - window 1): w_close close[i:iwindow] w_high high[i:iwindow] w_low low[i:iwindow] w_volume volume[i:iwindow] ret np.diff(w_close) / w_close[:-1] amp (w_high[1:] - w_low[1:]) / w_close[1:] vol_dev (w_volume[1:] - w_volume[1:].mean()) / (w_volume[1:].mean() 1e-10) close_dev (w_close[1:] - w_close[1:].max()) / (w_close[1:].max() 1e-10) pc np.column_stack([ret, amp, vol_dev, close_dev]) point_clouds.append(pc) return point_clouds point_clouds build_point_cloud(df, window) # 标准化 scaled_clouds [] for pc in point_clouds: scaler StandardScaler() scaled_clouds.append(scaler.fit_transform(pc)) # ---------------- 3. 持续同调计算 ---------------- diagrams_list [] for pc in scaled_clouds: diagrams ripser(pc, maxdim2)[dgms] diagrams_list.append(diagrams) # ---------------- 4. 压缩为因子 ---------------- def total_persistence(dgm): if len(dgm) 0: return 0.0 return np.sum(dgm[:, 1] - dgm[:, 0]) def max_persistence(dgm): if len(dgm) 0: return 0.0 return np.max(dgm[:, 1] - dgm[:, 0]) def persistence_entropy(dgm): if len(dgm) 0: return 0.0 pers dgm[:, 1] - dgm[:, 0] total pers.sum() if total 0: return 0.0 p pers / total p p[p 0] return -np.sum(p * np.log(p)) rows [] for diagrams in diagrams_list: row {} for dim in range(3): dgm diagrams[dim] row[ftotal_pers_H{dim}] total_persistence(dgm) row[fmax_pers_H{dim}] max_persistence(dgm) row[fentropy_H{dim}] persistence_entropy(dgm) rows.append(row) tda_features pd.DataFrame(rows) tda_features.index df.index[window - 1:] # ---------------- 5. 因子检验 ---------------- future_return df[close].pct_change(5).shift(-5).reindex(tda_features.index) analysis_df tda_features.copy() analysis_df[future_return] future_return analysis_df analysis_df.dropna() # IC 分析 for col in [total_pers_H0, total_pers_H1, max_pers_H0, max_pers_H1, entropy_H0, entropy_H1]: ic, p spearmanr(analysis_df[col], analysis_df[future_return]) print(f{col}: IC{ic:.4f}, p-value{p:.4f})8.2 运行结果解释在随机游走叠加分段趋势的数据中运行结果会显示 IC 值普遍偏低这是正常的。因为模拟数据本身没有明显可预测结构。真实行情数据中拓扑因子通常呈现以下特征在趋势行情中H1 总持久性可能较低因为点云接近一条平滑曲线。在震荡行情中H1 总持久性会升高因为价格在高维空间中形成环绕结构。在结构突变点附近H0 的连通分量数量变化和持久熵变化会比价格指标更早出现拐点。9. 常见问题与排查思路在实际工程中拓扑因子最容易出现以下几类问题。问题现象常见原因解决思路ripser 计算耗时过长窗口长度过大点云维度过高减小窗口长度对特征做 PCA 降维增加滑窗步长持久图中全是噪声点数据未标准化各维度量纲差异大使用 StandardScaler 或 MinMaxScalerH1、H2 结果全为空点云数量少于维度要求连通性无法形成window 至少大于特征维度 2因子 IC 极不稳定窗口长度不适合当前品种周期多窗口参数并行测试做参数敏感性分析不同品种迁移效果差品种波动率基线不同距离尺度不同按品种分别标准化或使用秩变换与其他因子相关性过高拓扑因子与传统波动率因子高度相关先做正交化再观察增量信息这里特别强调一下计算性能问题。持续同调计算的复杂度与点云数量有关。当前我们窗口取 30点云只有 29 个点计算很快。但如果把窗口放大到 120点云数量变成 119ripser 的计算时间会明显上升。在全市场多品种扫描时性能会成为瓶颈。工程上常用两种方式加速第一对点云做降维比如 PCA 降到 2-3 维后再做持续同调。第二使用更大滑窗步长例如每 3 个交易日计算一次因子减少重复计算。10. 拓扑因子的工程化与最佳实践10.1 参数选择与鲁棒性窗口长度是拓扑因子最重要的超参数。过短拓扑特征无法形成H1 空洞结构不稳定过长计算量大因子滞后性增加。建议做法是对多个窗口长度分别计算因子。每个窗口分别做 IC 分析。观察 IC 随窗口变化的规律选择稳定且经济含义明确的区间。如果多个窗口参数下因子方向一致说明该拓扑信号具有鲁棒性如果仅在单一参数下有效要警惕过拟合风险。10.2 因子正交化与增量信息实际多因子模型中拓扑因子需要和其他因子组合。建议先计算拓扑因子与已有因子的相关矩阵。如果拓扑因子和波动率因子、振幅因子相关性很高说明它更多反映的是“波动”维度增量价值有限。正交化的一种常见做法from sklearn.linear_model import LinearRegression # 假设已有基础因子 base_factor拓扑因子 tda_factor def orthogonalize(tda_factor, base_factors): 将 tda_factor 对 base_factors 做回归取残差作为正交化后的新因子 reg LinearRegression() reg.fit(base_factors, tda_factor) residual tda_factor - reg.predict(base_factors) return residual正交化后的残差因子代表拓扑因子中“无法被传统因子解释”的部分这部分增量信息才值得纳入组合。10.3 行情数据的合法性与环境安全在因子挖掘工程中要注意使用合规的数据源。国内行情数据可以通过官方渠道获得不要在未经授权的情况下爬取受限数据。涉及生产环境部署时数据库操作、数据变更必须有授权和备份机制。这里也要特别强调拓扑因子研究属于量化研究的常规技术探索不能用于操纵市场或破坏交易系统。所有研究必须在合法合规前提下开展。10.4 防止未来函数因子研究中未来函数是最致命的错误。构建标签时要用未来数据但因子值本身只能用 t 日及之前的信息。我们前面示例中用了shift(-5)生成标签同时因子值只依赖于当前窗口内的历史数据这是正确的。但有几种隐蔽的未来函数需要注意标准化时使用了全样本的均值和方差。滑窗计算过程中窗口边界处理错误导致包含 t1 日数据。选股时使用了 T 日收盘价但交易实际发生在 T1 日。在做 TDA 因子时标准化的 fit 只能基于当前窗口或滚动统计量不能使用整个数据集统一 fit。10.5 计算性能优化在实盘或回测场景中如果需要对上百个品种逐日计算拓扑因子性能优化是必须考虑的。推荐以下策略将点云构造和 ripser 计算提前离线批量处理。使用numba或 Cython 优化热点函数。对 H1 空洞结构不稳定的品种可以降低计算维度。使用多进程并行计算不同品种。11. 总结与应用展望本文从拓扑数据分析和因子挖掘的角度完整梳理了行情结构因子的实现路径。核心要点可以用一段话来概括行情数据并不只是一条价格曲线它在高维空间中具有形状结构。通过构建滑窗点云、计算持续同调、压缩拓扑特征我们可以把“市场结构状态”量化为可回测的因子。这种因子与传统的动量、波动率因子不同它捕捉的是数据形态层面的信息特别适合用于震荡/趋势状态识别、结构突变预警和风控过滤。下一步可以继续学习的方向包括将 H0、H1 特征与机器学习模型结合构建状态分类器。研究更复杂的距离度量例如基于 DTW动态时间规整的距离矩阵。尝试将拓扑特征与图神经网络结合构建空间-时间序列模型。在更长周期周线、月线数据上验证因子迁移能力。如果你是第一次接触拓扑数据分析建议先跑通本文的模拟数据流程再切换真实行情。真实数据上你会遇到更多噪声但拓扑因子在结构转换点附近的独特表现会给你带来不一样的量化视角。如果本文对你有帮助可以收藏备用实际使用中遇到其他问题也欢迎在评论区交流。
返回列表