大家好,我是量化老王。上一篇(实验002)咱们用成交量过滤了均线策略的假信号,让收益有了明显提升,但也留下了一个关键问题:策略里用的5日短期均线、20日长期均线,是凭经验选的,真的是最适合贵州茅台的参数吗?今天实验003,咱们就用网格搜索这个“参数优化神器”,系统筛选最优均线组合,让策略胜率再上一个台阶。建议先看完001、002篇,策略优化的逻辑链会更清晰。
先搞懂核心逻辑:什么是网格搜索?简单说,就是我们先设定一个“参数范围”(比如短期均线从3日到15日,长期均线从20日到60日),然后让程序像“扫雷”一样,遍历这个范围内的所有参数组合(比如3日+20日、3日+21日……15日+60日),再用每组参数跑策略回测,最后选出收益率最高的那组参数。这种方式比凭经验选参数更科学,能避免“主观偏见”。
实验前提:延续前两篇的环境(Python 3.8+)和数据源(akshare获取茅台2023年数据),核心库新增“itertools”(用于生成所有参数组合,Python内置库,无需额外安装),其他库还是pandas、matplotlib。
第一步:复用基础代码,获取数据并做好预处理。这部分代码和002篇基本一致,主要是获取茅台2023年日线数据(含收盘价、成交量),并计算成交量均值(用于复用成交量过滤逻辑)。
# 导入核心库import akshare as akimport pandas as pdimport matplotlib.pyplot as pltfrom itertools import product # 用于生成参数组合(内置库,无需安装)# 1. 获取贵州茅台2023年日线数据(含成交量)df = ak.stock_zh_a_hist(symbol="600519", period="daily", start_date="20230101", end_date="20231231", adjust="qfq") # 前复权数据# 2. 数据预处理df["日期"] = pd.to_datetime(df["日期"])df.set_index("日期", inplace=True)df = df.sort_index()# 重命名字段为英文,方便后续处理df.rename(columns={"开盘":"open", "收盘":"close", "最高":"high", "最低":"low", "成交量":"volume"}, inplace=True)# 计算5日成交量均值(复用002篇的过滤条件)df["volume_ma5"] = df["volume"].rolling(window=5).mean()print("数据预处理完成,预览前5行:")print(df[["close", "volume", "volume_ma5"]].head())这里重点说明:我们会完整复用002篇的“成交量过滤逻辑”,因为已经验证过它能有效剔除假信号,本次优化的核心只是“均线参数”,避免多变量干扰,让优化结果更可信。
第二步:定义参数范围和网格搜索函数。首先设定合理的参数范围(结合A股常见均线周期),再封装“策略逻辑+回测”的完整函数,方便后续批量调用。
# 1. 设定均线参数范围(关键:范围不能太宽,也不能太窄)short_window_range = range(3, 16) # 短期均线范围:3日~15日long_window_range = range(20, 61) # 长期均线范围:20日~60日# 生成所有可能的参数组合(比如(3,20)、(3,21)...(15,60))param_combinations = product(short_window_range, long_window_range)# 2. 封装完整策略函数(含成交量过滤+回测)def strategy_with_params(df, short_window, long_window): """ 带参数的策略函数:输入不同均线参数,返回策略回测结果 df: 预处理后的数据集 short_window: 短期均线周期 long_window: 长期均线周期 返回:策略总收益率 """ df_strategy = df.copy() # 计算对应周期的均线 df_strategy["short_ma"] = df_strategy["close"].rolling(window=short_window).mean() df_strategy["long_ma"] = df_strategy["close"].rolling(window=long_window).mean() # 生成基础金叉/死叉信号 df_strategy["golden_cross_base"] = (df_strategy["short_ma"].shift(1) < df_strategy["long_ma"].shift(1)) & (df_strategy["short_ma"] > df_strategy["long_ma"]) df_strategy["death_cross_base"] = (df_strategy["short_ma"].shift(1) > df_strategy["long_ma"].shift(1)) & (df_strategy["short_ma"] < df_strategy["long_ma"]) # 叠加成交量过滤(复用002篇逻辑:成交量>5日量均) df_strategy["golden_cross_valid"] = df_strategy["golden_cross_base"] & (df_strategy["volume"] > df_strategy["volume_ma5"]) df_strategy["death_cross_valid"] = df_strategy["death_cross_base"] & (df_strategy["volume"] > df_strategy["volume_ma5"]) # 生成交易信号 df_strategy["signal"] = None df_strategy.loc[df_strategy["golden_cross_valid"], "signal"] = 1 df_strategy.loc[df_strategy["death_cross_valid"], "signal"] = 0 # 内置简单回测(初始资金10万元,全仓买卖,含手续费!) initial_capital = 100000 commission_rate = 0.0003 # 手续费率:0.03%(A股常见佣金率,买卖都收) slippage_rate = 0.001 # 滑点率:0.1%(模拟实盘买卖差价,更贴近真实) df_strategy["position"] = 0 # 0=空仓,1=满仓 df_strategy["cash"] = initial_capital df_strategy["value"] = initial_capital share_num = 0 # 持股数量 for i in range(len(df_strategy)): current_signal = df_strategy.iloc[i]["signal"] current_position = df_strategy.iloc[i]["position"] current_close = df_strategy.iloc[i]["close"] # 买入逻辑(含手续费+滑点) if current_signal == 1 and current_position == 0: df_strategy.iloc[i, df_strategy.columns.get_loc("position")] = 1 # 滑点:买入时成本增加滑点率(实际买入价=收盘价*(1+滑点率)) buy_price = current_close * (1 + slippage_rate) # 计算可买股数(扣掉手续费后) available_cash = df_strategy.iloc[i]["cash"] share_num = available_cash // (buy_price * (1 + commission_rate)) # 扣除买入金额+手续费 total_cost = share_num * buy_price * (1 + commission_rate) df_strategy.iloc[i, df_strategy.columns.get_loc("cash")] = available_cash - total_cost # 卖出逻辑(含手续费+滑点) elif current_signal == 0 and current_position == 1: df_strategy.iloc[i, df_strategy.columns.get_loc("position")] = 0 # 滑点:卖出时收入减少滑点率(实际卖出价=收盘价*(1-滑点率)) sell_price = current_close * (1 - slippage_rate) # 计算卖出收入(扣掉手续费后) total_income = share_num * sell_price * (1 - commission_rate) df_strategy.iloc[i, df_strategy.columns.get_loc("cash")] = df_strategy.iloc[i]["cash"] + total_income share_num = 0 # 卖出后持股数归0 # 无信号:延续前一天状态 else: df_strategy.iloc[i, df_strategy.columns.get_loc("position")] = df_strategy.iloc[i-1]["position"] df_strategy.iloc[i, df_strategy.columns.get_loc("cash")] = df_strategy.iloc[i-1]["cash"] # 计算总资产 if df_strategy.iloc[i]["position"] == 1: # 持仓时,总资产=现金+持股市值(按当前收盘价计算) df_strategy.iloc[i, df_strategy.columns.get_loc("value")] = df_strategy.iloc[i]["cash"] + share_num * current_close else: df_strategy.iloc[i, df_strategy.columns.get_loc("value")] = df_strategy.iloc[i]["cash"] # 计算策略总收益率 final_value = df_strategy.iloc[-1]["value"] total_return = (final_value - initial_capital) / initial_capital * 100 return total_return, df_strategy # 返回收益率和带策略数据的数据集两个关键升级点:1)首次加入手续费和滑点(A股常见佣金率0.03%,滑点率0.1%),回测结果更贴近实盘,避免“纸上富贵”;2)将“均线计算、信号生成、成交量过滤、回测”封装成一个函数,后续只需传入不同参数,就能批量跑策略,这是量化分析的核心高效技巧。
第三步:执行网格搜索,筛选最优参数。遍历所有参数组合,记录每组参数的收益率,最后排序选出最优组合。
# 1. 初始化存储参数和收益率的字典param_results = {}# 2. 遍历所有参数组合,执行策略并记录结果(耐心等待,约1~2分钟)for short_win, long_win in param_combinations: # 跳过短期均线≥长期均线的无效组合(逻辑上短期要小于长期,否则交叉信号颠倒) if short_win >= long_win: continue # 调用策略函数,获取收益率 total_return, _ = strategy_with_params(df, short_win, long_win) # 存储参数组合和对应收益率 param_results[(short_win, long_win)] = total_return# 3. 筛选最优参数(收益率最高的组合)optimal_params = max(param_results.items(), key=lambda x: x[1])optimal_short = optimal_params[0][0]optimal_long = optimal_params[0][1]optimal_return = optimal_params[1]# 4. 输出网格搜索结果print("="*60)print("网格搜索结果汇总:")print(f"测试的参数组合总数:{len(param_results)}组")print(f"最优均线参数组合:短期{optimal_short}日 + 长期{optimal_long}日")print(f"该组合下策略总收益率:{optimal_return:.2f}%")print("="*60)# 对比002篇的默认参数(5日+20日)收益率,看优化效果default_return, _ = strategy_with_params(df, short_window=5, long_window=20)print(f"默认参数(5日+20日)策略总收益率:{default_return:.2f}%")print(f"参数优化后收益率提升:{optimal_return - default_return:.2f}个百分点")print("="*60)这里有个细节:必须跳过“短期均线≥长期均线”的组合(比如10日+8日),因为这种组合的金叉、死叉逻辑会完全颠倒,不符合趋势跟随的核心逻辑。运行后会发现,最优参数大概率不是默认的5日+20日(比如可能是6日+25日、8日+30日等),且收益率比默认参数高不少(通常能提升10~20个百分点)。
第四步:验证最优参数策略,可视化结果。用筛选出的最优参数重新跑策略,生成信号图,直观验证效果。
# 1. 用最优参数跑策略,获取完整数据optimal_return, df_optimal = strategy_with_params(df, optimal_short, optimal_long)# 2. 可视化最优参数策略的信号(上下分栏:股价+信号 / 成交量)plt.rcParams["font.sans-serif"] = ["SimHei"]plt.rcParams["axes.unicode_minus"] = Falsefig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), gridspec_kw={"height_ratios": [3, 1]})# 上半部分:股价+最优均线+有效信号ax1.plot(df_optimal["close"], label="贵州茅台收盘价", color="blue", linewidth=1.5)ax1.plot(df_optimal["short_ma"], label=f"最优短期均线({optimal_short}日)", color="orange", linewidth=1.2)ax1.plot(df_optimal["long_ma"], label=f"最优长期均线({optimal_long}日)", color="green", linewidth=1.2)# 买入信号(红色上箭头)ax1.scatter(df_optimal[df_optimal["signal"] == 1].index, df_optimal[df_optimal["signal"] == 1]["close"], color="red", marker="^", s=120, label="有效买入信号", zorder=5)# 卖出信号(绿色下箭头)ax1.scatter(df_optimal[df_optimal["signal"] == 0].index, df_optimal[df_optimal["signal"] == 0]["close"], color="green", marker="v", s=120, label="有效卖出信号", zorder=5)ax1.set_title(f"Python量化策略实验003:最优均线参数策略信号图({optimal_short}日+{optimal_long}日,含量能过滤)", fontsize=14)ax1.set_ylabel("价格(元)", fontsize=12)ax1.legend(loc="upper left")ax1.grid(True, alpha=0.3)# 下半部分:成交量+5日量均ax2.bar(df_optimal.index, df_optimal["volume"], color="gray", alpha=0.6, label="当日成交量")ax2.plot(df_optimal["volume_ma5"], color="red", linewidth=1.2, label="5日成交量均值")ax2.set_xlabel("日期", fontsize=12)ax2.set_ylabel("成交量(手)", fontsize=12)ax2.legend()ax2.grid(True, alpha=0.3)plt.tight_layout()plt.show()# 输出最优策略的关键统计信息signal_count = df_optimal["signal"].notna().sum()buy_count = (df_optimal["signal"] == 1).sum()sell_count = (df_optimal["signal"] == 0).sum()print(f"\n最优参数策略关键统计:")print(f"2023年总交易信号数:{signal_count}个(买入{buy_count}个,卖出{sell_count}个)")print(f"初始资金:100000元")print(f"最终总资产:{df_optimal.iloc[-1]['value']:.2f}元")print(f"总收益率:{optimal_return:.2f}%")可视化后能清晰看到:最优参数下的信号更少但更精准,比如在2023年的几次关键行情转折点(如4月上涨、10月下跌),都能精准发出信号,且每次信号都伴随成交量放大(符合量价配合逻辑)。同时,交易次数比默认参数更少,减少了手续费和滑点的损耗,这也是收益率提升的重要原因。
关键总结与后续优化方向:1)本次核心是“网格搜索参数优化”,核心逻辑是“遍历+筛选”,这是量化策略调优的基础方法,适用于大多数简单策略;2)优化后仍有局限:当前策略只适用于茅台单只股票,能否适配其他股票?且没有考虑最大回撤(风险指标);3)下一篇(实验004)咱们解决“普适性”问题——将策略改造成多股票版本,同时加入最大回撤、夏普比率等风险指标,让策略更全面。
互动环节:你跑代码后得到的最优均线参数是多少?收益率比默认参数提升了多少?评论区晒出你的结果,和其他量化爱好者交流!关注我,100篇Python量化实验干货持续更新,从单股策略到多股组合,逐步解锁专业量化技能!
转载请注明来自海坡下载,本文标题:《均线优化策略(Python量化策略实验003网格搜索优化均线参数)》
京公网安备11000000000001号
京ICP备11000001号
还没有评论,来说两句吧...