欢迎加入专注于财经数据与量化投研的【数据科学实战】知识星球!在这里,您将获取持续更新的《财经数据宝典》和《量化投研宝典》,这两部宝典相辅相成,为您在量化投研道路上提供明确指引。 《量化投研宝典》精选了业内持续维护且实用性强的开源工具(Backtrader、Qlib、VeighNa等),配合详细教程与代码示例,帮助您快速构建量化策略;《财经数据宝典》则汇集了多年财经数据维护经验,全面介绍从 AKShare、Tushare 到 Wind、iFind 等国内外数据源,并附有丰富的使用技巧。 无论您是量化投资新手还是经验丰富的研究者,星球社区都能帮您少走弯路,事半功倍,共同探索数据驱动的投资世界!
引言
在量化交易的世界里,如何构建一个既能获得超额收益又能控制风险的交易系统,一直是投资者追求的目标。今天,我们将深入探讨一个结合 TSI(真实强度指数)和 GAPO(价格振荡指标)的双指标交易策略。通过 Python 实现完整的回测框架,包括动态参数优化和蒙特卡洛模拟验证,让你了解如何构建和验证一个稳健的交易系统。
策略核心思想
这个交易系统的核心逻辑非常简单:
- 入场信号:当 TSI 从负值穿越到正值,且 GAPO 低于阈值时买入
- 出场信号:当 TSI 从正值穿越到负值,且 GAPO 低于阈值时卖出
TSI 指标用于捕捉市场动量,GAPO 指标用于识别市场波动率,两者结合可以在低波动环境下捕捉趋势转折点。
完整代码实现
让我们从导入必要的库开始:
import numpy as np
import pandas as pd
import yfinance as yf # 用于获取股票数据
import vectorbt as vbt # 用于回测框架
import itertools # 用于生成参数组合
1. TSI 指标计算
TSI 是一个动量振荡器,通过双重指数移动平均来平滑价格变化:
def calculate_tsi(close, long=25, short=13):
"""
计算 TSI(真实强度指数)
参数:
close: 收盘价序列
long: 长期 EMA 周期
short: 短期 EMA 周期
"""
# 计算价格变化
diff = close.diff()
# 计算价格变化的绝对值
abs_diff = diff.abs()
# 双重 EMA 平滑
double_ema_diff = diff.ewm(span=long).mean().ewm(span=short).mean()
double_ema_abs = abs_diff.ewm(span=long).mean().ewm(span=short).mean()
# 计算 TSI
tsi = 100 * (double_ema_diff / double_ema_abs)
return tsi
2. GAPO 指标计算
GAPO 指标用于衡量价格的波动性:
def calculate_gapo(df, period=10):
"""
计算 GAPO(价格振荡指标)
参数:
df: 包含 High 和 Low 列的数据框
period: 计算周期
"""
high = df['High']
low = df['Low']
gapo = []
for i in range(period, len(df)):
# 获取周期内的最高价和最低价
h = high[i - period:i].max()
l = low[i - period:i].min()
if h != l:
r = h - l
log_r = np.log10(r + 1e-10)
# 避免对 0 取对数
log_n = np.log10(period)
gapo_val = 1 - (log_r / log_n)
else:
gapo_val = 0
gapo.append(gapo_val)
return pd.Series([np.nan]*period + gapo, index=df.index)
3. 信号生成器
结合两个指标生成交易信号:
def generate_signals_tsi_gapo(df, tsi_long, tsi_short, tsi_prev_shift,
gapo_period, gapo_threshold):
"""
生成交易信号
参数:
df: 股票数据
tsi_long: TSI 长期参数
tsi_short: TSI 短期参数
tsi_prev_shift: TSI 前值偏移
gapo_period: GAPO 周期
gapo_threshold: GAPO 阈值
"""
df = df.copy()
# 计算指标
df['TSI'] = calculate_tsi(df['Close'], long=tsi_long, short=tsi_short)
df['GAPO'] = calculate_gapo(df, period=gapo_period)
df['TSI_prev'] = df['TSI'].shift(tsi_prev_shift)
# 生成入场信号:TSI 由负转正且 GAPO 低于阈值
entry_signal = (
(df['TSI_prev'] 0) &
(df['TSI'] > 0) &
(df['GAPO'] < gapo_threshold)
)
# 生成出场信号:TSI 由正转负且 GAPO 低于阈值
exit_signal = (
(df['TSI_prev'] > 0) &
(df['TSI'] 0) &
(df['GAPO'] < gapo_threshold)
)
return entry_signal, exit_signal
4. Walk-Forward 优化
Walk-Forward 优化是一种动态参数优化方法,它使用滚动窗口的方式,在历史数据上训练参数,然后在未来数据上测试:
def walk_forward_optimization(df, start_year, end_year):
"""
Walk-Forward 优化
使用 4 年训练数据,1 年测试数据的滚动窗口
"""
results = []
# 生成所有参数组合
param_combinations = generate_parameter_combinations()
for test_year in range(start_year + 4, end_year + 1):
# 定义训练期
train_start = test_year - 4
train_end = test_year - 1
train_data = df[(df.index.year >= train_start) &
(df.index.year <= train_end)]
best_params = None
best_performance = -np.inf
# 遍历所有参数组合
for params in param_combinations:
tsi_long, tsi_short, tsi_shift, gapo_period, gapo_thresh = params
# 生成信号
entries, exits = generate_signals_tsi_gapo(
train_data, tsi_long, tsi_short,
tsi_shift, gapo_period, gapo_thresh
)
# 回测
pf = vbt.Portfolio.from_signals(
close=train_data['Open'],
entries=entries.shift(1).astype(bool).fillna(False),
exits=exits.shift(1).astype(bool).fillna(False),
init_cash=100_000,
fees=0.001, # 0.1% 手续费
slippage=0.002, # 0.2% 滑点
freq='D'
)
# 记录最佳参数
performance = pf.total_return()
if performance > best_performance:
best_performance = performance
best_params = params
results.append({
'Year': test_year,
'Best_Params': best_params
})
return pd.DataFrame(results)
实战案例:CVS 股票回测
让我们在 CVS(CVS Health Corporation)股票上应用这个策略:
# 下载数据
symbol = 'CVS'
start_date = '2015-01-01'
end_date = '2025-01-01'
df = yf.download(symbol, start=start_date, end=end_date)
# 运行优化
results = walk_forward_optimization(df, 2016, 2025)
print("Walk Forward 优化结果:")
print(results)
优化结果显示,不同年份的最优参数有所不同,这体现了市场的动态性:
- 2020-2021 年:TSI(30, 15),GAPO(5, 0.2)
- 2022 年:TSI(30, 10),GAPO(10, 0.3)
- 2023-2024 年:TSI(15, 5),GAPO(15, 0.2)
性能评估
1. 基础性能指标
策略在 2020-2025 年的表现:
相比之下,买入持有策略的表现:
2. Alpha 和 Beta 分析
为了更深入地理解策略的表现,我们计算了相对于买入持有策略的 Alpha 和 Beta:
# 计算 Alpha、Beta 和相关性
from scipy.stats import linregress
# 获取策略和基准的收益率
portfolio_returns = portfolio.returns()
benchmark_returns = benchmark.returns()
# 线性回归计算 Beta 和 Alpha
slope, intercept, r_value, _, _ = linregress(
benchmark_returns, portfolio_returns
)
beta = slope
alpha = intercept
correlation = r_value
print(f"Alpha: {alpha:.4f}") # 0.0004
print(f"Beta: {beta:.4f}") # 0.2030
print(f"相关性: {correlation:.4f}") # 0.4505
结果解读:
- Alpha = 0.0004:日均超额收益 0.04%,年化约 10.6%
- Beta = 0.203:策略波动性仅为市场的 20%,风险显著降低
- 相关性 = 0.45:与市场中度相关,具有一定的独立性
3. 蒙特卡洛模拟验证
为了验证策略的稳健性,我们使用区块引导法(Block Bootstrap)进行蒙特卡洛模拟:
# 区块引导蒙特卡洛模拟
n_simulations = 1000
block_size = 5 # 每个区块包含 5 个连续交易日
# 统计指标
below_benchmark_return_count = 0
above_benchmark_drawdown_count = 0
for _ in range(n_simulations):
# 随机抽取区块并重组
block_indices = np.random.randint(
0, len(returns) - block_size + 1,
size=n_blocks
)
sampled_returns = np.concatenate(
[returns[i:i+block_size] for i in block_indices]
)[:n_days]
# 计算模拟路径的指标
equity = init_value * np.cumprod(1 + sampled_returns)
total_return = (equity[-1] / init_value) - 1
max_dd = np.max(1 - equity / np.maximum.accumulate(equity))
# 统计表现不如基准的情况
if total_return < benchmark_return:
below_benchmark_return_count += 1
if max_dd > benchmark_drawdown:
above_benchmark_drawdown_count += 1
模拟结果(1000 次):
策略优势与局限
优势
- 风险控制出色:最大回撤从 57% 降至 20%,降幅近三分之二
- 低市场暴露:仅 16.84% 的时间持仓,大幅降低市场风险
- 参数自适应:Walk-Forward 优化确保参数随市场变化调整
局限
-
交易次数较少:5 年仅 14 笔交易,统计显著性有限
总结
本文介绍了一个基于 TSI 和 GAPO 双指标的量化交易策略,通过 Python 实现了完整的回测框架。策略在 CVS 股票上展现出优秀的风险调整后收益,特别是在市场下跌期间仍能获得正收益。
关键要点:
- 双指标结合可以提高信号质量,TSI 捕捉动量,GAPO 过滤波动
- Walk-Forward 优化是验证策略稳健性的重要方法
- 风险控制比追求高收益更重要,降低回撤是长期盈利的关键
需要注意的是,历史回测结果不代表未来表现,实盘交易前需要充分考虑市场风险。建议读者在理解策略原理的基础上,根据自己的风险偏好进行调整和优化。
参考文章
加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取完整研究解析、详细回测框架代码实现和完整策略逻辑实操指南。财经数据与量化投研知识社区
核心权益如下:
- 赠送《财经数据宝典》完整文档,汇集多年财经数据维护经验
- 赠送《量化投研宝典》完整文档,汇集多年量化投研领域经验
- 赠送《PyBroker-入门及实战》视频课程,手把手学习量化策略开发
星球已有丰富内容积累,包括量化投研论文、财经高频数据、 PyBroker 视频教程、定期直播、数据分享和答疑解难。适合对量化投研和财经数据分析有兴趣的学习者及从业者。欢迎加入我们!