保姆级教程:用Python和Pandas手搓一个ETF网格交易回测脚本(附完整代码)

📅 2026/7/1 5:58:38
保姆级教程:用Python和Pandas手搓一个ETF网格交易回测脚本(附完整代码)
Python量化实战从零构建ETF网格交易回测系统最近两年越来越多普通投资者开始关注量化交易这个曾经只属于机构玩家的领域。而网格交易作为量化策略中最容易理解也最适合个人实践的方法之一正在吸引大量Python爱好者的目光。今天我们就来彻底拆解如何用Python和Pandas构建一个完整的ETF网格交易回测系统——不仅会给出可直接运行的代码更重要的是解释每个环节的设计思路和量化逻辑。1. 网格交易基础与准备工作网格交易本质上是一种低买高卖的机械化操作策略。它的核心思想是在标的资产价格下跌时分批买入在价格上涨时分批卖出通过价格波动获取收益。这种策略特别适合震荡市行情而ETF因其低费率、高流动性和分散风险的特点成为网格交易的理想标的。1.1 环境配置与数据准备开始之前确保你的Python环境已安装以下关键库pip install pandas numpy matplotlib quantstats对于回测数据推荐使用Tushare或者AKShare获取ETF历史数据。这里我们以沪深300ETF代码510300为例import pandas as pd import numpy as np import matplotlib.pyplot as plt import quantstats as qs # 读取准备好的ETF历史数据 data pd.read_csv(510300.csv, parse_dates[date]) data data.set_index(date).sort_index() print(data.head())提示实际应用中建议使用复权价格进行计算避免分红送股对价格的影响。1.2 网格策略参数设计网格交易有几个关键参数需要预先确定基准价格网格的中心锚点通常选择建仓时的价格网格间距决定买卖触发点的间隔常用百分比表示每格资金量每次买入或卖出的金额或份额最大持仓防止过度加仓的风险控制参数这些参数的选择直接影响策略的表现和风险特征我们将在后续章节详细分析各参数的优化方法。2. 核心回测逻辑实现2.1 初始化交易账户任何回测系统都需要模拟一个虚拟交易账户。我们需要跟踪以下关键变量# 初始化账户状态 initial_capital 100000 # 初始资金10万元 position 0 # 初始持仓为0 cash initial_capital # 初始现金等于初始资金 portfolio_value [] # 记录每日组合价值 trade_log [] # 交易记录2.2 网格交易引擎这是整个回测系统的核心部分实现了网格策略的买卖逻辑def grid_trading_engine(data, initial_price, grid_size0.03, unit_cash5000): 网格交易回测引擎 :param data: 包含价格数据的DataFrame :param initial_price: 初始基准价格 :param grid_size: 网格间距(百分比) :param unit_cash: 每格交易金额 :return: 回测结果字典 current_price initial_price benchmark initial_price position 0 cash initial_capital trades [] for date, row in data.iterrows(): high, low row[high], row[low] # 卖出逻辑价格突破上网格线 while high benchmark * (1 grid_size): if position 0: trade_price benchmark * (1 grid_size) trade_shares unit_cash / trade_price position - trade_shares cash trade_price * trade_shares trades.append([date, sell, trade_price, trade_shares]) benchmark trade_price high trade_price # 防止同一价格多次触发 else: break # 买入逻辑价格跌破下网格线 while low benchmark * (1 - grid_size): if cash unit_cash: trade_price benchmark * (1 - grid_size) trade_shares unit_cash / trade_price position trade_shares cash - trade_price * trade_shares trades.append([date, buy, trade_price, trade_shares]) benchmark trade_price low trade_price # 防止同一价格多次触发 else: break return { final_position: position, final_cash: cash, trades: pd.DataFrame(trades, columns[date, type, price, shares]) }2.3 收益计算与绩效评估回测完成后我们需要对策略表现进行量化评估def evaluate_strategy(data, trades, initial_capital): # 计算每日持仓价值 portfolio pd.DataFrame(indexdata.index) portfolio[price] data[close] portfolio[position] 0 portfolio[cash] initial_capital portfolio[value] initial_capital # 重建持仓变化 current_position 0 current_cash initial_capital for date, group in trades.groupby(date): daily_trades group.groupby(type).sum() if buy in daily_trades.index: current_position daily_trades.loc[buy, shares] current_cash - (daily_trades.loc[buy, price] * daily_trades.loc[buy, shares]).sum() if sell in daily_trades.index: current_position - daily_trades.loc[sell, shares] current_cash (daily_trades.loc[sell, price] * daily_trades.loc[sell, shares]).sum() portfolio.loc[date:, position] current_position portfolio.loc[date:, cash] current_cash # 计算每日组合价值 portfolio[value] portfolio[position] * portfolio[price] portfolio[cash] return portfolio3. 策略优化与参数调校3.1 网格间距的影响分析网格间距是策略最敏感的参数之一。间距太小会导致过度交易增加摩擦成本间距太大则可能错过交易机会。我们可以通过参数扫描找到最优值grid_sizes [0.01, 0.02, 0.03, 0.04, 0.05] results [] for size in grid_sizes: result grid_trading_engine(data, initial_price5.0, grid_sizesize) final_value result[final_cash] result[final_position] * data.iloc[-1][close] results.append({grid_size: size, final_value: final_value}) pd.DataFrame(results).set_index(grid_size).plot()3.2 资金管理优化合理的资金管理可以显著提升策略的稳健性。考虑以下改进方向动态调整每格资金量根据波动率调整交易金额网格不对称设计上涨和下跌采用不同间距止损机制在极端行情下保护资本一个改进版的资金管理模块可能如下def dynamic_unit_cash(volatility, base5000): 根据波动率动态调整每格交易金额 if volatility 0.1: return base * 0.8 elif volatility 0.3: return base * 1.5 else: return base4. 完整回测系统集成4.1 将各模块组装成完整系统现在我们将前面开发的各个模块整合成一个完整的回测系统class GridTradingBacktest: def __init__(self, data, initial_capital100000): self.data data self.initial_capital initial_capital self.initial_price data.iloc[0][close] def run(self, grid_size0.03, unit_cash5000): # 运行回测引擎 engine_result grid_trading_engine( self.data, initial_priceself.initial_price, grid_sizegrid_size, unit_cashunit_cash ) # 评估策略表现 portfolio evaluate_strategy( self.data, engine_result[trades], self.initial_capital ) # 计算绩效指标 returns portfolio[value].pct_change().fillna(0) sharpe qs.stats.sharpe(returns) max_drawdown qs.stats.max_drawdown(returns) return { portfolio: portfolio, trades: engine_result[trades], metrics: { sharpe: sharpe, max_drawdown: max_drawdown, final_return: (portfolio[value].iloc[-1] / self.initial_capital - 1) * 100 } }4.2 可视化与结果分析良好的可视化能帮助我们直观理解策略行为def visualize_results(backtest_result): fig, (ax1, ax2) plt.subplots(2, 1, figsize(12, 8), sharexTrue) # 价格和交易点 portfolio backtest_result[portfolio] trades backtest_result[trades] ax1.plot(portfolio.index, portfolio[price], labelPrice) buy_trades trades[trades[type] buy] sell_trades trades[trades[type] sell] ax1.scatter(buy_trades[date], buy_trades[price], colorred, labelBuy) ax1.scatter(sell_trades[date], sell_trades[price], colorgreen, labelSell) ax1.set_ylabel(Price) ax1.legend() # 组合价值 ax2.plot(portfolio.index, portfolio[value], labelPortfolio Value) ax2.set_ylabel(Value) ax2.legend() plt.tight_layout() plt.show()5. 实战进阶技巧5.1 处理现实中的复杂情况实际应用中会遇到许多理论回测中不会出现的问题滑点影响实际成交价与预期价格的差异交易费用佣金和印花税对收益的侵蚀流动性限制大额订单的市场冲击一个考虑交易成本的改进版本def apply_transaction_cost(trades, commission0.0003, stamp_duty0.001): 应用交易成本到交易记录 trades[cost] 0 for i, row in trades.iterrows(): if row[type] buy: trades.at[i, cost] row[price] * row[shares] * commission else: trades.at[i, cost] row[price] * row[shares] * (commission stamp_duty) return trades5.2 多品种网格策略分散投资可以降低单一品种的风险。扩展我们的系统支持多ETF网格class MultiAssetGrid: def __init__(self, assets_data, capital_allocation): :param assets_data: 字典key为资产名value为价格DataFrame :param capital_allocation: 字典key为资产名value为分配资金比例 self.assets assets_data self.allocation capital_allocation def run(self, grid_params): results {} for asset, data in self.assets.items(): backtester GridTradingBacktest(data, initial_capitalself.allocation[asset]) results[asset] backtester.run(**grid_params) return results在开发量化交易系统的过程中最常遇到的坑是过度拟合历史数据。记得在任何策略投入实盘前都要进行充分的样本外测试和参数鲁棒性检验。