均线系统的参数选择与信号逻辑
均线系统可能是量化交易里被研究最多的技术指标,但大多数人只是简单调用 talib.SMA 然后画个图。真正的问题是:均线参数怎么定?信号怎么过滤? 本文从实战角度拆解这三个问题。
一、均线周期的选择逻辑
均线周期的选择不是拍脑袋,它取决于你的交易周期 和持仓目标。
1.1 短、中、长周期的划分
| 周期类型 | 常用参数 | 适用场景 |
|---|---|---|
| 短线 | 5, 10, 20 | 日内或1-5天持仓 |
| 中线 | 30, 60, 90 | 5-20天持仓 |
| 长线 | 120, 250 | 季度级持仓 |
关键原则:均线周期必须与K线周期匹配。如果你用日线数据,5日均线代表一周的持仓成本;如果你用小时线,5小时均线只代表半天。
1.2 参数选择的两个实用方法
方法一:基于市场波动率(ATR)自适应
python
import pandas as pd
import numpy as np
def adaptive_ma_period(close, atr_period=14, base_period=20):
"""
根据ATR波动率调整均线周期
波动大时用短周期,波动小时用长周期
"""
# 计算ATR
high = close.rolling(2).max()
low = close.rolling(2).min()
tr = pd.concat([high - low,
(high - close.shift(1)).abs(),
(low - close.shift(1)).abs()], axis=1).max(axis=1)
atr = tr.rolling(atr_period).mean()
# 归一化ATR到合理范围
atr_norm = atr / close
# 波动率越大,周期越短
period = base_period * (atr_norm.mean() / atr_norm)
return period.clip(5, 60).round().fillna(base_period)
方法二:参数网格扫描(Walk-forward)
python
def scan_ma_params(close, param_range=range(5, 100, 5)):
"""
遍历不同均线参数,计算收益风险比
返回最优参数
"""
results = {}
for period in param_range:
ma = close.rolling(period).mean()
# 简单的信号:价格在均线上方做多,下方做空
signal = np.where(close > ma, 1, -1)
returns = close.pct_change().fillna(0)
strategy_returns = signal * returns
# 计算夏普比率(年化)
sharpe = strategy_returns.mean() / strategy_returns.std() * np.sqrt(252)
results[period] = sharpe
best_period = max(results, key=results.get)
return best_period, results
二、金叉死叉信号逻辑
金叉死叉是最基础的均线信号,但直接使用效果很差。原因在于:信号太频繁,噪音太大。
2.1 基础信号实现
python
def golden_cross_signal(df, fast_period=5, slow_period=20):
"""
金叉死叉信号生成
df: DataFrame with 'close' column
返回: signal (1=做多, -1=做空, 0=无信号)
"""
df = df.copy()
df['ma_fast'] = df['close'].rolling(fast_period).mean()
df['ma_slow'] = df['close'].rolling(slow_period).mean()
# 金叉: fast上穿slow
df['cross_up'] = (df['ma_fast'] > df['ma_slow']) & (df['ma_fast'].shift(1) <= df['ma_slow'].shift(1))
# 死叉: fast下穿slow
df['cross_down'] = (df['ma_fast'] < df['ma_slow']) & (df['ma_fast'].shift(1) >= df['ma_slow'].shift(1))
# 生成信号,死叉优先
df['signal'] = 0
df.loc[df['cross_up'], 'signal'] = 1
df.loc[df['cross_down'], 'signal'] = -1
# 持仓状态(直到反向信号出现)
df['position'] = df['signal'].replace(0, np.nan).ffill().fillna(0)
return df
2.2 信号过滤的三个实用技巧
技巧1:斜率过滤
只接受均线本身方向一致的交叉信号:
python
def filtered_cross_signal(df, fast=5, slow=20, slope_period=3):
"""
带斜率过滤的金叉死叉
要求慢线本身也在向上/向下
"""
df = df.copy()
df['ma_slow'] = df['close'].rolling(slow).mean()
# 计算慢线斜率
df['slope'] = df['ma_slow'].diff(slope_period)
# 基础交叉信号
df = golden_cross_signal(df, fast, slow)
# 过滤:金叉时要求慢线向上,死叉时要求慢线向下
df['signal_filtered'] = np.where(
(df['signal'] == 1) & (df['slope'] > 0), 1,
np.where((df['signal'] == -1) & (df['slope'] < 0), -1, 0)
)
df['position_filtered'] = df['signal_filtered'].replace(0, np.nan).ffill().fillna(0)
return df
技巧2:价格确认过滤
金叉后等待价格收盘价确认突破:
python
def confirmed_cross_signal(df, fast=5, slow=20, confirm_bars=2):
"""
金叉后等待N根K线确认
"""
df = df.copy()
df = golden_cross_signal(df, fast, slow)
# 记录交叉发生的位置
df['cross_idx'] = np.where(df['signal'] != 0, df.index, np.nan)
df['cross_idx'] = df['cross_idx'].ffill()
# 确认:交叉后价格持续站在快线上方/下方
df['ma_fast'] = df['close'].rolling(fast).mean()
df['above_fast'] = df['close'] > df['ma_fast']
# 金叉后确认
df['confirm'] = 0
for i in range(1, len(df)):
if df['signal'].iloc[i] == 1:
# 检查后续confirm_bars根K线是否都在快线上方
future_above = df['above_fast'].iloc[i+1:i+1+confirm_bars].all()
if future_above:
df.loc[df.index[i+1], 'confirm'] = 1
return df
技巧3:ATR波动率过滤
python
def atr_filtered_signal(df, fast=5, slow=20, atr_mult=1.5):
"""
用ATR过滤假交叉
交叉发生时,价格变动幅度必须超过ATR的倍数
"""
df = df.copy()
df = golden_cross_signal(df, fast, slow)
# 计算ATR
high = df['close'].rolling(2).max()
low = df['close'].rolling(2).min()
tr = pd.concat([high - low,
(high - df['close'].shift(1)).abs(),
(low - df['close'].shift(1)).abs()], axis=1).max(axis=1)
df['atr'] = tr.rolling(14).mean()
# 计算交叉时的价格变动
df['price_change'] = df['close'].diff()
# 过滤条件
df['signal_atr'] = np.where(
(df['signal'] == 1) & (df['price_change'].abs() > df['atr'] * atr_mult), 1,
np.where((df['signal'] == -1) & (df['price_change'].abs() > df['atr'] * atr_mult), -1, 0)
)
return df
三、多均线组合策略
单组均线信号太脆弱,实战中更推荐多均线组合。常见的有三均线系统 和均线排列系统。
3.1 三均线系统
python
def triple_ma_strategy(df, short=5, mid=20, long=60):
"""
三均线策略
做多条件: short > mid > long (多头排列)
做空条件: short < mid < long (空头排列)
"""
df = df.copy()
df['ma_short'] = df['close'].rolling(short).mean()
df['ma_mid'] = df['close'].rolling(mid).mean()
df['ma_long'] = df['close'].rolling(long).mean()
# 多头排列
df['bullish'] = (df['ma_short'] > df['ma_mid']) & (df['ma_mid'] > df['ma_long'])
# 空头排列
df['bearish'] = (df['ma_short'] < df['ma_mid']) & (df['ma_mid'] < df['ma_long'])
# 信号
df['signal'] = 0
df.loc[df['bullish'] & ~df['bullish'].shift(1).fillna(False), 'signal'] = 1
df.loc[df['bearish'] & ~df['bearish'].shift(1).fillna(False), 'signal'] = -1
df['position'] = df['signal'].replace(0, np.nan).ffill().fillna(0)
return df
3.2 均线排列打分系统
python
def ma_alignment_score(df, periods=[5, 10, 20, 30, 60]):
"""
均线排列打分
所有均线多头排列得5分,空头排列得-5分
中间状态按排列顺序打分
"""
df = df.copy()
# 计算所有均线
for p in periods:
df[f'ma_{p}'] = df['close'].rolling(p).mean()
# 计算排列得分
score = np.zeros(len(df))
ma_cols = [f'ma_{p}' for p in periods]
for i in range(len(df)):
ma_values = df[ma_cols].iloc[i].values
# 检查是否严格递增(多头排列)
if all(ma_values[j] < ma_values[j+1] for j in range(len(ma_values)-1)):
score[i] = 5
# 严格递减(空头排列)
elif all(ma_values[j] > ma_values[j+1] for j in range(len(ma_values)-1)):
score[i] = -5
else:
# 部分排列,按正序对数量打分
pairs = sum(1 for j in range(len(ma_values)-1) if ma_values[j] < ma_values[j+1])
score[i] = pairs - (len(ma_values)-1-pairs)
df['ma_score'] = score
return df
四、参数优化方法
参数优化最怕过拟合。这里给两个实用方法。
4.1 滚动窗口优化
python
def rolling_optimize(df, param_grid, window=250, step=50):
"""
滚动窗口参数优化
每step天重新优化一次参数
"""
results = []
for start in range(0, len(df)-window, step):
end = start + window
train_data = df.iloc[start:end]
# 在训练窗口内找最优参数
best_sharpe = -np.inf
best_param = None
for period in param_grid:
ma = train_data['close'].rolling(period).mean()
signal = np.where(train_data['close'] > ma, 1, -1)
returns = train_data['close'].pct_change().fillna(0)
strategy_returns = signal * returns
sharpe = strategy_returns.mean() / strategy_returns.std() * np.sqrt(252)
if sharpe > best_sharpe:
best_sharpe = sharpe
best_param = period
results.append({
'train_start': df.index[start],
'train_end': df.index[end],
'best_param': best_param,
'best_sharpe': best_sharpe
})
return pd.DataFrame(results)
4.2 参数稳定性检验
python
def parameter_stability_test(df, param_range=range(10, 50, 5)):
"""
检验参数的稳定性
如果相邻参数的表现差异过大,说明参数不稳定
"""
sharpe_dict = {}
for period in param_range:
ma = df['close'].rolling(period).mean()
signal = np.where(df['close'] > ma, 1, -1)
returns = df['close'].pct_change().fillna(0)
strategy_returns = signal * returns
sharpe = strategy_returns.mean() / strategy_returns.std() * np.sqrt(252)
sharpe_dict[period] = sharpe
# 计算相邻参数的表现差异
periods = list(sharpe_dict.keys())
diffs = []
for i in range(len(periods)-1):
diff = abs(sharpe_dict[periods[i+1]] - sharpe_dict[periods[i]])
diffs.append(diff)
avg_diff = np.mean(diffs)
# 如果平均差异小于0.1,说明参数比较稳定
stable = avg_diff < 0.1
return sharpe_dict, avg_diff, stable
五、实战注意事项
-
数据频率:均线系统的参数必须与K线周期匹配。日线用20/60,小时线用20/60可能就不合适。
-
信号延迟:均线系统天然滞后。趋势越强,滞后越严重。可以结合价格突破来提前入场。
-
震荡市失效:均线系统在震荡市会被反复打脸。建议加一个趋势过滤器(比如ADX>25才算趋势)。
-
参数敏感性:如果参数从20改成21,策略表现就大变,说明参数不稳定,容易过拟合。
-
组合使用:单组均线信号胜率有限,建议多周期均线共振(比如日线多头+小时线多头)。
六、完整示例代码
python
import pandas as pd
import numpy as np
import yfinance as yf
# 获取数据(示例用,实际替换成自己的数据源)
df = yf.download('000300.SS', start='2020-01-01', end='2024-01-01')
df.columns = ['open', 'high', 'low', 'close', 'adj_close', 'volume']
# 1. 基础金叉死叉
basic_signal = golden_cross_signal(df, 5, 20)
# 2. 带斜率过滤
filtered_signal = filtered_cross_signal(df, 5, 20)
# 3. 三均线策略
triple_signal = triple_ma_strategy(df, 5, 20, 60)
# 4. 参数优化
best_param, sharpe_results = scan_ma_params(df['close'])
# 5. 参数稳定性检验
stability_test = parameter_stability_test(df)
# 输出结果对比
print(f"基础信号年化收益: {basic_signal['position'].shift(1) * df['close'].pct_change().fillna(0):.4f}")
print(f"最优均线参数: {best_param}")
print(f"参数稳定性: {'稳定' if stability_test[2] else '不稳定'}")
均线系统的核心在于参数选择与信号过滤。参数选择要考虑市场特性,信号过滤要控制交易频率。多均线组合和稳定性检验是避免过拟合的有效手段。更多内容请关注本站后续更新。