欢迎加入专注于财经数据与量化投研的【数据科学实战】知识星球!在这里,您将获取持续更新的《财经数据宝典》和《量化投研宝典》,这两部宝典相辅相成,为您在量化投研道路上提供明确指引。 我们提供了精选的国内外量化投研的 180+ 篇高质量文章,并每日更新最新研究成果,涵盖策略开发、因子分析、风险管理等核心领域。 无论您是量化投资新手还是经验丰富的研究者,星球社区都能帮您少走弯路,事半功倍,共同探索数据驱动的投资世界!
引言
在量化交易中,如何在追踪趋势的同时控制风险,一直是交易者们关注的核心问题。今天给大家介绍一个结合了 Kaufman 自适应移动平均线(KAMA)和平均真实波幅(ATR)的交易策略。这个策略的核心思想是:用 KAMA 捕捉趋势,用 ATR 控制风险,再通过 Optuna 自动优化参数。
这篇文章将带你从零开始理解这个策略的原理和实现方法,即使你是 Python 新手,也能跟着代码一步步学习。
策略的核心思想
这个策略主要由三个部分组成:
KAMA 自适应趋势跟踪:不同于简单移动平均线(SMA)或指数移动平均线(EMA),KAMA 会根据市场波动自动调整平滑程度。在趋势明显时反应迅速,在震荡行情中则更加平滑,避免频繁交易。
ATR 波动率过滤:ATR 用来衡量价格波动的剧烈程度。当波动率过高时,策略会避免入场;如果持仓期间波动率突然飙升,则立即止损出场。
Optuna 参数优化:不用手动猜测最佳参数,让 Optuna 自动搜索最优的 KAMA 周期和 ATR 阈值组合。
代码实现
1. 获取历史数据
首先,我们需要获取股票的历史价格数据。这里使用 Financial Modeling Prep API:
import pandas as pd
import requests
def fetch_data_fmp(symbol: str, start_date: str, end_date: str, api_key: str):
"""
从 Financial Modeling Prep API 获取历史价格数据
参数:
symbol: 股票代码,如 'TSLA'
start_date: 开始日期,格式 'YYYY-MM-DD'
end_date: 结束日期,格式 'YYYY-MM-DD'
api_key: API 密钥
返回:
包含日期、开盘价、最高价、最低价、收盘价、成交量的 DataFrame
"""
url = f"https://financialmodelingprep.com/api/v3/historical-price-full/{symbol}?from={start_date}&to={end_date}&apikey={api_key}"
resp = requests.get(url)
resp.raise_for_status()
data = resp.json().get('historical', [])
df = pd.DataFrame(data)
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values('date').reset_index(drop=True)
return df[['date', 'open', 'high', 'low', 'close', 'volume']]
2. 计算 KAMA 指标
KAMA 的核心是效率比率(ER)。当价格频繁变向时,ER 较低,KAMA 平滑程度更高;当价格趋势明显时,ER 较高,KAMA 更紧密跟随价格。
import numpy as np
def kaufman_adaptive_moving_average(df, er_period, fast_period, slow_period):
"""
计算 Kaufman 自适应移动平均线
参数:
df: 包含收盘价的 DataFrame
er_period: 效率比率计算周期
fast_period: 快速平滑常数周期
slow_period: 慢速平滑常数周期
返回:
添加了 KAMA 列的 DataFrame
"""
close = df['close'].values
# 计算价格变化幅度
change = abs(close[er_period:] - close[:-er_period])
# 计算波动性(价格变化的累加)
volatility = np.sum(abs(close[1:] - close[:-1]).reshape(-1, er_period), axis=1)
# 效率比率 = 价格变化 / 波动性
er = change / volatility
# 计算平滑常数
sc_fast = 2 / (fast_period + 1)
sc_slow = 2 / (slow_period + 1)
sc = (er * (sc_fast - sc_slow) + sc_slow) ** 2
# 计算 KAMA
kama = np.zeros(len(close))
kama[:er_period] = close[:er_period]
for i in range(er_period, len(close)):
kama[i] = kama[i-1] + sc[i-er_period] * (close[i] - kama[i-1])
df['KAMA'] = kama
return df
3. 计算 ATR 指标
ATR 用于衡量价格波动的幅度,是风险控制的关键指标:
def average_true_range(df, window=14):
"""
计算平均真实波幅(ATR)
参数:
df: 包含最高价、最低价、收盘价的 DataFrame
window: ATR 计算窗口,默认 14 天
返回:
添加了 ATR 列的 DataFrame
"""
# 计算真实波幅的三个组成部分
high_low = df['high'] - df['low'] # 当日最高价减最低价
high_close = abs(df['high'] - df['close'].shift()) # 当日最高价减昨日收盘价
low_close = abs(df['low'] - df['close'].shift()) # 当日最低价减昨日收盘价
# 取三者中的最大值作为真实波幅
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
# 计算移动平均得到 ATR
atr = tr.rolling(window).mean()
df['ATR'] = atr
return df
4. 生成交易信号
根据 KAMA 和 ATR 生成买卖信号:
def generate_signals(df, atr_min_pct, atr_max_pct, atr_exit_pct):
"""
生成交易信号
买入条件:价格在 KAMA 之上,且 ATR 百分比在合理范围内
卖出条件:价格跌破 KAMA,或 ATR 百分比超过退出阈值
参数:
df: 包含 KAMA 和 ATR 的 DataFrame
atr_min_pct: ATR 最小百分比阈值
atr_max_pct: ATR 最大百分比阈值
atr_exit_pct: ATR 退出百分比阈值
返回:
添加了持仓信号的 DataFrame
"""
# 计算 ATR 占收盘价的百分比
df['atr_pct'] = df['ATR'] / df['close'] * 100
df['position'] = 0
for i in range(1, len(df)):
# 买入信号:价格高于 KAMA 且波动率在可接受范围内
if df['close'][i] > df['KAMA'][i] and atr_min_pct <= df['atr_pct'][i] < atr_max_pct:
df.at[i, 'position'] = 1
# 卖出信号:价格低于 KAMA 或波动率过高
elif df['close'][i] < df['KAMA'][i] or df['atr_pct'][i] > atr_exit_pct:
df.at[i, 'position'] = 0
else:
# 保持前一天的持仓状态
df.at[i, 'position'] = df.at[i-1, 'position']
return df
5. 回测逻辑
模拟交易过程,计算最终收益:
def backtest(df, initial_balance=1000):
"""
回测策略表现
参数:
df: 包含持仓信号的 DataFrame
initial_balance: 初始资金,默认 1000
返回:
最终账户余额
"""
balance = initial_balance # 账户余额
position = 0 # 持仓数量
entry_price = 0 # 入场价格
for i in range(len(df)):
# 开仓:从无仓位变为有仓位
if position == 0 and df['position'][i] == 1:
position = balance / df['close'][i] # 全仓买入
entry_price = df['close'][i]
balance = 0
# 平仓:从有仓位变为无仓位
elif position > 0 and df['position'][i] == 0:
balance = position * df['close'][i] # 卖出所有持仓
position = 0
# 如果最后还有持仓,按最后收盘价结算
if position > 0:
balance = position * df['close'].iloc[-1]
return balance
6. 使用 Optuna 优化参数
让 Optuna 自动搜索最优参数组合:
import optuna
def objective(trial):
"""
Optuna 优化目标函数
自动搜索最优的策略参数组合
"""
# 定义参数搜索范围
er_period = trial.suggest_int('er_period', 2, 30) # 效率比率周期
fast_period = trial.suggest_int('fast_period', 2, 20) # 快速周期
slow_period = trial.suggest_int('slow_period', 20, 100) # 慢速周期
atr_window = trial.suggest_int('atr_window', 5, 30) # ATR 窗口
atr_min_pct = trial.suggest_uniform('atr_min_pct', 0.5, 3) # ATR 最小百分比
atr_max_pct = trial.suggest_uniform('atr_max_pct', 1, 5) # ATR 最大百分比
atr_exit_pct = trial.suggest_uniform('atr_exit_pct', 5, 20) # ATR 退出百分比
# 复制数据并应用策略
df_copy = df.copy()
df_copy = kaufman_adaptive_moving_average(df_copy, er_period, fast_period, slow_period)
df_copy = average_true_range(df_copy, atr_window)
df_copy = generate_signals(df_copy, atr_min_pct, atr_max_pct, atr_exit_pct)
# 返回最终收益作为优化目标
final_balance = backtest(df_copy)
return final_balance
# 创建优化研究并运行
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
实战案例:TSLA 回测结果
以 TSLA(特斯拉)股票为例,使用 Sharpe 比率作为优化指标,策略表现如下:
从结果可以看出,策略收益远超买入持有策略,同时通过 ATR 过滤有效控制了波动率较高时期的风险。
总结
这个 KAMA + ATR 策略的优势在于:
KAMA 的自适应特性使其在趋势行情和震荡行情中都能保持较好的表现,避免了传统均线在震荡期频繁产生假信号的问题。ATR 过滤机制则在高波动时期暂停交易,有效降低了策略的回撤风险。Optuna 自动优化省去了手动调参的繁琐工作,提高了策略开发效率。
当然,需要注意的是,任何基于历史数据优化的策略都存在过拟合风险,实盘交易前务必进行充分的样本外测试。
参考文章
加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。财经数据与量化投研知识社区
核心权益如下:
- 赠送《财经数据宝典》完整文档,汇集多年财经数据维护经验
- 赠送《量化投研宝典》完整文档,汇集多年量化投研领域经验
- 赠送《PyBroker-入门及实战》视频课程,手把手学习量化策略开发
- 每日分享高质量量化投研文章(已更新 180+篇)、代码和相关资料
星球已有丰富内容积累,包括量化投研论文、财经高频数据、 PyBroker 视频教程、定期直播、数据分享和答疑解难。适合对量化投研和财经数据分析有兴趣的学习者及从业者。欢迎加入我们!