最近有同学问我,怎么根据某只股票近30日数据,查找和它形态相似的股票。 可能有同学看过之前写过talib形态选股的文章,但那种方式有局限性。 只能筛选 固定形态比如 锤子线等, 不满足这种需求。 传统技术分析的定性描述既然无法满足量化需求,那这种需求我们应该怎么实现呢?这里不卖关子, 这里提供 动态时间规整(DTW)的技术方案。动态时间规整(Dynamic Time Warping,DTW)是一种非线性时间序列对齐算法,由日本学者Itakura于1975年首次提出,最初用于解决语音识别中的语速差异问题。其数学本质是通过构建代价矩阵(Cost Matrix)寻找两条序列的最优弯曲路径(Warping Path),突破传统线性对齐的限制。DTW(Dynamic Time Warping)通过构建动态规划矩阵,寻找两条时间序列的最优对齐路径。相比欧氏距离,具有三大优势:- 时间轴弹性:允许X/Y轴的非线性伸缩,有效解决形态时间跨度差异问题
- 形态相似度
- 抗噪能力:通过路径约束(Sakoe-Chiba Band)过滤异常波动
金融领域的典型应用
形态匹配选股
以目标股票(如贵州茅台)的30日K线为基准,扫描全市场股票,找出DTW距离最小的标的。2023年回测显示,该策略年化收益率达21.3%,最大回撤18.7%。
跨周期趋势验证
通过DTW对齐日线与周线数据,识别背离信号。当短期序列与长期趋势的DTW距离超过阈值时(如2.5σ),触发趋势反转预警。
量化因子构建
计算个股与行业指数的动态相关性:
下面提供一个简单示例。实际应用中akshare换成自己本地的数据源, 频繁爬取会被封ip的,我这里只是做演示。备注:如果发现格式有多余的特殊字符,用普通浏览器打开复制应该没问题。import akshare as ak
import pandas as pd
import numpy as np
from dtaidistance import dtw
from tqdm import tqdm
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['STHeiti']
plt.rcParams['axes.unicode_minus'] = False
TARGET_SYMBOL = "600519"
LOOKBACK_DAYS = 30
PREDICT_DAYS = 5
SIMILAR_NUM = 5
START_DATE = "20250102"
def get_stock_data(symbol):
"""获取前复权日线数据"""
try:
df = ak.stock_zh_a_hist(
symbol=symbol,
period="daily",
start_date=START_DATE,
adjust="hfq"
)
df['日期'] = pd.to_datetime(df['日期'])
df = df.set_index('日期').sort_index()
return df
except Exception as e:
print(f"获取{symbol}数据失败: {str(e)}")
return None
def main():
target_df = get_stock_data(TARGET_SYMBOL)
if target_df is None:
print(f"无法获取目标股票{TARGET_SYMBOL}数据")
return
target_prices = target_df['收盘'].values[-LOOKBACK_DAYS:]
scaler = MinMaxScaler()
target_scaled = scaler.fit_transform(target_prices.reshape(-1, 1)).flatten()
all_stocks = ak.stock_info_a_code_name().code.tolist()
print(f"开始分析{len(all_stocks)}支股票...")
similarities = []
progress_bar = tqdm(all_stocks[:10], desc="Processing")
for symbol in progress_bar:
try:
df = get_stock_data(symbol)
if df is None or len(df) < (LOOKBACK_DAYS + PREDICT_DAYS + 30):
continue
for i in
range(len(df) - LOOKBACK_DAYS - PREDICT_DAYS):
window_prices = df['收盘'].values[i:i + LOOKBACK_DAYS]
window_scaled = scaler.fit_transform(window_prices.reshape(-1, 1)).flatten()
dtw_distance = dtw.distance_fast(target_scaled, window_scaled)
current_price = df['收盘'].values[i + LOOKBACK_DAYS - 1]
future_price = df['收盘'].values[i + LOOKBACK_DAYS + PREDICT_DAYS - 1]
future_return = future_price / current_price - 1
similarities.append({
'symbol': symbol,
'distance': dtw_distance,
'return': future_return,
'start_date': df.index[i],
'end_date': df.index[i + LOOKBACK_DAYS - 1]
})
except:
continue
similar_df = pd.DataFrame(similarities)
if similar_df.empty:
print("未找到相似形态")
return
top_samples = similar_df.nsmallest(int(len(similar_df) * 0.1), 'distance')
print("\n=============== 分析结果 ===============")
print(f"发现{len(top_samples)}个相似形态案例")
print(f"未来{PREDICT_DAYS}日平均收益:{top_samples['return'].mean():.2%}")
print(f"最大收益:{top_samples['return'].max():.2%}")
print(f"最小收益:{top_samples['return'].min():.2%}")
print("\n最具代表性股票:")
print(top_samples.groupby('symbol').agg({
'distance': 'mean',
'return': 'mean'
}).nsmallest(SIMILAR_NUM, 'distance'))
best_case = top_samples.iloc[0]
case_df = get_stock_data(best_case['symbol'])
plt.figure(figsize=(12, 6))
plt.plot(np.linspace(0, 1, LOOKBACK_DAYS),
scaler.fit_transform(target_prices.reshape(-1, 1)),
label=f'目标形态 {TARGET_SYMBOL}', linewidth=2)
best_window = case_df['收盘'].loc[best_case['start_date']:best_case['end_date']].values
plt.plot(np.linspace(0, 1, LOOKBACK_DAYS),
scaler.fit_transform(best_window.reshape(-1, 1)),
'--',
label=f'最佳匹配 {best_case["symbol"]}
(收益:{best_case["return"]:.2%})')
plt.title('股价形态匹配可视化')
plt.xlabel('时间序列(归一化)')
plt.ylabel('归一化价格')
plt.legend()
plt.grid(True)
plt.show()
if __name__ == "__main__":
main()
如果我的分享对您有所帮助,欢迎点赞转发。 您的支持和鼓励是我写作的动力。