一、前言
策略回测是量化交易中验证策略有效性的重要环节。一个完善的回测系统可以帮助我们评估策略表现,发现潜在问题。本文将详细介绍如何构建和使用回测系统。
本文将介绍:
- 回测系统设计
- 回测指标计算
- 回测结果分析
- 回测陷阱避免
- 实盘与回测差异
二、为什么选择天勤量化(TqSdk)
TqSdk回测支持:
| 功能 | 说明 |
|---|---|
| 历史数据 | 支持获取高质量历史数据 |
| 回测框架 | 内置回测功能 |
| 数据完整性 | 数据完整可靠 |
| 灵活扩展 | 支持自定义回测逻辑 |
安装方法:
pipinstalltqsdk pandas numpy三、回测系统设计
3.1 回测框架
#!/usr/bin/env python# -*- coding: utf-8 -*-""" 功能:策略回测系统 说明:本代码仅供学习参考 """fromtqsdkimportTqApi,TqAuthimportpandasaspdimportnumpyasnpfromdatetimeimportdatetimeclassBacktestEngine:"""回测引擎"""def__init__(self,initial_capital=100000,commission=0.0001):self.initial_capital=initial_capital self.capital=initial_capital self.commission=commission self.position=0self.entry_price=0self.trades=[]self.equity_curve=[]defexecute_trade(self,signal,price,datetime):"""执行交易"""ifsignal==0:return# 计算手续费commission_cost=price*abs(signal-self.position)*self.commission# 平仓ifself.position!=0andsignal!=self.position:pnl=(price-self.entry_price)*self.position self.capital+=pnl-commission_cost self.trades.append({'entry_time':self.entry_time,'exit_time':datetime,'entry_price':self.entry_price,'exit_price':price,'position':self.position,'pnl':pnl,'commission':commission_cost})self.position=0# 开仓ifself.position==0andsignal!=0:self.position=signal self.entry_price=price self.entry_time=datetime self.capital-=commission_cost# 记录权益曲线current_equity=self.capitalifself.position!=0:unrealized_pnl=(price-self.entry_price)*self.position current_equity+=unrealized_pnl self.equity_curve.append({'datetime':datetime,'equity':current_equity})defget_results(self):"""获取回测结果"""equity_df=pd.DataFrame(self.equity_curve)equity_df.set_index('datetime',inplace=True)returns=equity_df['equity'].pct_change().dropna()total_return=(self.capital-self.initial_capital)/self.initial_capitaliflen(self.trades)>0:winning_trades=[tfortinself.tradesift['pnl']>0]win_rate=len(winning_trades)/len(self.trades)avg_win=np.mean([t['pnl']fortinwinning_trades])ifwinning_tradeselse0avg_loss=np.mean([t['pnl']fortinself.tradesift['pnl']<0])ifany(t['pnl']<0fortinself.trades)else0else:win_rate=0avg_win=0avg_loss=0# 计算最大回撤cumulative=(1+returns).cumprod()running_max=cumulative.expanding().max()drawdown=(cumulative-running_max)/running_max max_drawdown=drawdown.min()# 计算夏普比率sharpe_ratio=returns.mean()/returns.std()*np.sqrt(252)ifreturns.std()>0else0return{'total_return':total_return,'win_rate':win_rate,'avg_win':avg_win,'avg_loss':avg_loss,'max_drawdown':max_drawdown,'sharpe_ratio':sharpe_ratio,'total_trades':len(self.trades),'equity_curve':equity_df}# 使用示例backtest=BacktestEngine(initial_capital=100000)四、策略回测实现
4.1 双均线策略回测
fromtqsdk.tafuncimportmadefbacktest_ma_strategy(api,symbol,fast_period=5,slow_period=20):"""回测双均线策略"""klines=api.get_kline_serial(symbol,3600,1000)api.wait_update()backtest=BacktestEngine(initial_capital=100000)foriinrange(slow_period,len(klines)):current_klines=klines.iloc[:i+1]ma_fast=ma(current_klines['close'],fast_period)ma_slow=ma(current_klines['close'],slow_period)# 生成信号signal=0ifma_fast.iloc[-1]>ma_slow.iloc[-1]andma_fast.iloc[-2]<=ma_slow.iloc[-2]:signal=1elifma_fast.iloc[-1]<ma_slow.iloc[-1]andma_fast.iloc[-2]>=ma_slow.iloc[-2]:signal=-1# 执行交易current_price=klines['close'].iloc[i]current_time=klines.index[i]backtest.execute_trade(signal,current_price,current_time)returnbacktest.get_results()# 使用示例api=TqApi(auth=TqAuth("快期账户","快期密码"))results=backtest_ma_strategy(api,"SHFE.rb2510")print(f"总收益率:{results['total_return']:.2%}")print(f"胜率:{results['win_rate']:.2%}")print(f"最大回撤:{results['max_drawdown']:.2%}")print(f"夏普比率:{results['sharpe_ratio']:.2f}")api.close()4.2 多策略回测
defbacktest_multiple_strategies(api,symbol,strategies):"""回测多个策略"""klines=api.get_kline_serial(symbol,3600,1000)api.wait_update()results={}forstrategy_name,strategy_funcinstrategies.items():backtest=BacktestEngine(initial_capital=100000)foriinrange(20,len(klines)):current_klines=klines.iloc[:i+1]signal=strategy_func(current_klines)current_price=klines['close'].iloc[i]current_time=klines.index[i]backtest.execute_trade(signal,current_price,current_time)results[strategy_name]=backtest.get_results()returnresults# 使用示例defma_strategy(klines):ma5=ma(klines['close'],5)ma20=ma(klines['close'],20)ifma5.iloc[-1]>ma20.iloc[-1]andma5.iloc[-2]<=ma20.iloc[-2]:return1elifma5.iloc[-1]<ma20.iloc[-1]andma5.iloc[-2]>=ma20.iloc[-2]:return-1return0strategies={'双均线策略':ma_strategy}results=backtest_multiple_strategies(api,"SHFE.rb2510",strategies)forname,resultinresults.items():print(f"{name}: 收益率{result['total_return']:.2%}")五、回测指标
5.1 收益指标
defcalculate_return_metrics(equity_curve):"""计算收益指标"""returns=equity_curve['equity'].pct_change().dropna()total_return=(equity_curve['equity'].iloc[-1]-equity_curve['equity'].iloc[0])/equity_curve['equity'].iloc[0]annual_return=(1+total_return)**(252/len(equity_curve))-1return{'total_return':total_return,'annual_return':annual_return,'avg_daily_return':returns.mean()}5.2 风险指标
defcalculate_risk_metrics(equity_curve):"""计算风险指标"""returns=equity_curve['equity'].pct_change().dropna()# 波动率volatility=returns.std()*np.sqrt(252)# 最大回撤cumulative=(1+returns).cumprod()running_max=cumulative.expanding().max()drawdown=(cumulative-running_max)/running_max max_drawdown=drawdown.min()# VaRvar_95=np.percentile(returns,5)return{'volatility':volatility,'max_drawdown':max_drawdown,'var_95':var_95}5.3 综合指标
defcalculate_comprehensive_metrics(equity_curve,risk_free_rate=0.03):"""计算综合指标"""returns=equity_curve['equity'].pct_change().dropna()# 夏普比率excess_returns=returns-risk_free_rate/252sharpe=excess_returns.mean()/excess_returns.std()*np.sqrt(252)ifexcess_returns.std()>0else0# 索提诺比率downside_returns=returns[returns<0]sortino=excess_returns.mean()/downside_returns.std()*np.sqrt(252)iflen(downside_returns)>0anddownside_returns.std()>0else0# Calmar比率total_return=(equity_curve['equity'].iloc[-1]-equity_curve['equity'].iloc[0])/equity_curve['equity'].iloc[0]annual_return=(1+total_return)**(252/len(equity_curve))-1cumulative=(1+returns).cumprod()running_max=cumulative.expanding().max()drawdown=(cumulative-running_max)/running_max max_drawdown=abs(drawdown.min())calmar=annual_return/max_drawdownifmax_drawdown>0else0return{'sharpe_ratio':sharpe,'sortino_ratio':sortino,'calmar_ratio':calmar}六、回测结果分析
6.1 结果可视化
importmatplotlib.pyplotaspltdefvisualize_backtest_results(results):"""可视化回测结果"""fig,axes=plt.subplots(2,2,figsize=(15,10))equity_curve=results['equity_curve']# 权益曲线axes[0,0].plot(equity_curve.index,equity_curve['equity'])axes[0,0].set_title('权益曲线')axes[0,0].set_ylabel('权益')# 收益率分布returns=equity_curve['equity'].pct_change().dropna()axes[0,1].hist(returns,bins=50)axes[0,1].set_title('收益率分布')axes[0,1].set_xlabel('收益率')# 回撤曲线cumulative=(1+returns).cumprod()running_max=cumulative.expanding().max()drawdown=(cumulative-running_max)/running_max axes[1,0].fill_between(drawdown.index,drawdown,0,alpha=0.3)axes[1,0].set_title('回撤曲线')axes[1,0].set_ylabel('回撤')# 月度收益monthly_returns=returns.resample('M').apply(lambdax:(1+x).prod()-1)axes[1,1].bar(monthly_returns.index,monthly_returns)axes[1,1].set_title('月度收益')axes[1,1].set_ylabel('收益率')plt.tight_layout()plt.savefig('backtest_results.png')plt.close()# 使用示例visualize_backtest_results(results)七、回测陷阱
7.1 常见陷阱
| 陷阱 | 说明 | 解决方法 |
|---|---|---|
| 未来函数 | 使用未来数据 | 严格按时间顺序 |
| 过拟合 | 过度优化参数 | 样本外验证 |
| 幸存者偏差 | 只测试成功策略 | 全面测试 |
| 数据质量 | 数据不准确 | 使用可靠数据源 |
7.2 避免方法
defavoid_backtest_traps():"""避免回测陷阱的方法"""tips={'未来函数':'确保不使用未来数据,按时间顺序处理','过拟合':'使用样本外数据验证,避免过度优化','数据质量':'使用可靠数据源,检查数据完整性','交易成本':'考虑手续费和滑点','流动性':'考虑市场流动性限制'}returntips八、实盘与回测差异
8.1 主要差异
| 差异 | 说明 |
|---|---|
| 滑点 | 实际成交价与预期价差 |
| 手续费 | 实际手续费可能不同 |
| 流动性 | 大单可能无法成交 |
| 延迟 | 网络和执行延迟 |
8.2 调整方法
defadjust_for_reality(backtest_results,slippage=0.0001,commission=0.0001):"""调整回测结果以接近实盘"""# 考虑滑点和手续费adjusted_return=backtest_results['total_return']-slippage-commissionreturnadjusted_return九、总结
9.1 回测要点
| 要点 | 说明 |
|---|---|
| 数据质量 | 使用高质量数据 |
| 避免陷阱 | 避免常见回测陷阱 |
| 全面分析 | 多角度分析结果 |
| 实盘调整 | 考虑实盘差异 |
9.2 注意事项
- 数据质量- 确保数据准确完整
- 避免过拟合- 使用样本外验证
- 考虑成本- 考虑交易成本
- 实盘验证- 回测后需要实盘验证
免责声明:本文仅供学习交流使用,不构成任何投资建议。期货交易有风险,入市需谨慎。
更多资源:
- 天勤量化官网:https://www.shinnytech.com
- GitHub开源地址:https://github.com/shinnytech/tqsdk-python
- 官方文档:https://doc.shinnytech.com/tqsdk/latest