双均线策略
构建一个包含数据获取、策略逻辑、回测引擎和结果可视化的完整框架,并使用经典的双均线策略作为示例。
📈 量化交易回测系统代码
数据获取,增加了策略逻辑、回测模拟和可视化分析。
import baostock as bs
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt# 设置中文字体,确保图表正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = Falsedef main():# 登录Baostock系统lg = bs.login()if lg.error_code != '0':print(f'登录失败: {lg.error_msg}')returntry:# 获取股票历史数据stock_code = "sh.600109" # 国金证券rs = bs.query_history_k_data_plus(stock_code,"date,open,high,low,close,volume,amount",start_date='2024-01-01', # 扩展日期范围以计算长期均线end_date='2024-10-10', frequency="d")# 将数据转换为DataFramedata_list = []while (rs.error_code == '0') & rs.next():data_list.append(rs.get_row_data())if not data_list:print("未获取到数据,请检查股票代码和日期范围。")returndf = pd.DataFrame(data_list, columns=rs.fields)# 数据预处理:转换数据类型并设置日期索引df['date'] = pd.to_datetime(df['date'])df.set_index('date', inplace=True)for col in ['open', 'high', 'low', 'close', 'volume', 'amount']:df[col] = df[col].astype(float)print("数据预览:")print(df.head())print(f"\n数据时间段: {df.index.min()} 至 {df.index.max()}")print(f"总交易日数: {len(df)}")# 应用双均线策略df = dual_moving_average_strategy(df)# 执行回测portfolio_df = backtest_strategy(df, initial_capital=100000)# 评估并打印回测结果print_backtest_results(portfolio_df)# 可视化回测结果visualize_backtest_results(df, portfolio_df, stock_code)except Exception as e:print(f"程序执行过程中发生错误: {e}")finally:# 确保登出系统bs.logout()print("\n已退出Baostock系统。")def dual_moving_average_strategy(df, short_window=5, long_window=20):"""双均线策略:当短期均线上穿长期均线时买入,下穿时卖出。"""# 计算短期和长期简单移动平均线(SMA)df['short_ma'] = df['close'].rolling(window=short_window).mean()df['long_ma'] = df['close'].rolling(window=long_window).mean()# 生成交易信号:短期均线上穿长期均线为1(买入),下穿为-1(卖出)df['signal'] = 0df.loc[df['short_ma'] > df['long_ma'], 'signal'] = 1df.loc[df['short_ma'] < df['long_ma'], 'signal'] = -1# 计算实际的交易点位(信号发生变化时)df['positions'] = df['signal'].diff()# 过滤掉初始的NaN值和信号未变化的点df['buy_signal'] = (df['positions'] == 2) # 信号从-1或0变为1df['sell_signal'] = (df['positions'] == -2) # 信号从1或0变为-1return dfdef backtest_strategy(df, initial_capital=100000):"""简单的回测引擎:模拟策略执行过程,计算投资组合价值。"""# 初始化回测所需列df['holdings'] = 0 # 持有股数df['cash'] = float(initial_capital) # 现金df['total'] = float(initial_capital) # 总资产df['returns'] = 0.0 # 每日收益率current_holdings = 0current_cash = initial_capitalfor i in range(1, len(df)):# 获取当前价格和前一天的信号current_price = df['close'].iloc[i]prev_signal = df['signal'].iloc[i-1]current_signal = df['signal'].iloc[i]# 根据信号执行交易# 信号变为买入且现金充足时,全仓买入if prev_signal <= 0 and current_signal == 1 and current_cash > 0:shares_to_buy = current_cash // current_pricecurrent_holdings = shares_to_buycurrent_cash -= shares_to_buy * current_price# 信号变为卖出且持有股票时,全仓卖出elif prev_signal >= 0 and current_signal == -1 and current_holdings > 0:current_cash += current_holdings * current_pricecurrent_holdings = 0# 更新持仓数据df.iloc[i, df.columns.get_loc('holdings')] = current_holdingsdf.iloc[i, df.columns.get_loc('cash')] = current_cashdf.iloc[i, df.columns.get_loc('total')] = current_cash + current_holdings * current_price# 计算收益率(从第二天开始)df['returns'] = df['total'].pct_change()# 创建专门用于结果分析的数据框portfolio_df = df[['close', 'total', 'returns']].copy()portfolio_df['benchmark_returns'] = df['close'].pct_change() # 基准(买入持有)收益率return portfolio_dfdef print_backtest_results(portfolio_df):"""打印回测的关键绩效指标。"""# 过滤掉无收益数据的第一天returns_df = portfolio_df.dropna()if len(returns_df) == 0:print("无有效回测数据。")return# 计算策略总收益率strategy_total_return = (returns_df['total'].iloc[-1] / returns_df['total'].iloc[0] - 1) * 100# 计算基准(买入持有)总收益率benchmark_total_return = (returns_df['close'].iloc[-1] / returns_df['close'].iloc[0] - 1) * 100# 计算年化收益率(简化计算)days = len(returns_df)strategy_annual_return = (1 + strategy_total_return/100) ** (252/days) - 1benchmark_annual_return = (1 + benchmark_total_return/100) ** (252/days) - 1# 计算最大回撤cumulative_max = returns_df['total'].expanding().max()drawdown = (returns_df['total'] - cumulative_max) / cumulative_maxmax_drawdown = drawdown.min() * 100print("\n" + "="*50)print("回测结果概览")print("="*50)print(f"策略总收益率: {strategy_total_return:.2f}%")print(f"基准总收益率(买入持有): {benchmark_total_return:.2f}%")print(f"策略年化收益率: {strategy_annual_return*100:.2f}%")print(f"基准年化收益率: {benchmark_annual_return*100:.2f}%")print(f"最大回撤: {max_drawdown:.2f}%")print(f"交易天数: {days}天")print("="*50)def visualize_backtest_results(df, portfolio_df, stock_code):"""可视化回测结果:价格走势、交易信号和资产变化。"""# 创建包含两个子图的图表fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))# 子图1:价格走势和交易信号ax1.plot(df.index, df['close'], label='收盘价', color='black', linewidth=1)ax1.plot(df.index, df['short_ma'], label='短期均线', color='blue', linewidth=1)ax1.plot(df.index, df['long_ma'], label='长期均线', color='red', linewidth=1)# 标记买入信号buy_signals = df[df['buy_signal'] == True]ax1.scatter(buy_signals.index, buy_signals['close'], color='green', marker='^', s=100, label='买入', alpha=0.7)# 标记卖出信号sell_signals = df[df['sell_signal'] == True]ax1.scatter(sell_signals.index, sell_signals['close'], color='red', marker='v', s=100, label='卖出', alpha=0.7)ax1.set_title(f'{stock_code} - 双均线策略交易信号')ax1.set_ylabel('价格 (元)')ax1.legend()ax1.grid(True, alpha=0.3)# 子图2:资产价值变化ax2.plot(portfolio_df.index, portfolio_df['total'], label='策略总资产', color='blue', linewidth=2)ax2.plot(portfolio_df.index, portfolio_df['close'] / portfolio_df['close'].iloc[0] * portfolio_df['total'].iloc[0], label='基准(买入持有)', color='gray', linewidth=2, linestyle='--')ax2.set_title('策略表现 vs 基准')ax2.set_ylabel('资产价值 (元)')ax2.set_xlabel('日期')ax2.legend()ax2.grid(True, alpha=0.3)plt.tight_layout()plt.show()if __name__ == "__main__":main()
💡 代码核心解析
这个代码构成了一个完整的量化回测系统,主要包含以下模块:
- 数据获取与预处理:使用Baostock获取原始数据,并转换为Pandas DataFrame,进行数据类型转换和日期索引设置。
- 策略逻辑 (
dual_moving_average_strategy
):实现了经典的双均线策略。当短期均线(如5日)上穿长期均线(如20日)时生成买入信号,下穿时生成卖出信号。 - 回测引擎 (
backtest_strategy
):模拟真实交易,根据策略信号进行买入和卖出操作,并跟踪记录持仓、现金和总资产的变化。 - 绩效评估 (
print_backtest_results
):计算并输出关键绩效指标(KPI),如总收益率、年化收益率、最大回撤等,并与简单的"买入持有"策略进行对比。 - 结果可视化 (
visualize_backtest_results
):将价格走势、交易信号以及策略与基准的资产曲线直观展示出来。
🚀 如何优化与进阶
这只是一个基础框架。要让策略更接近实战,可以从以下几个方面深入:
优化方向 | 具体建议 |
---|---|
引入更多数据 | 添加交易量指标验证信号有效性,或引入大盘指数数据(如沪深300)以控制系统性风险。 |
完善策略逻辑 | 增加止损/止盈机制,或结合其他技术指标(如RSI、MACD)进行多重过滤,减少假信号。 |
精细化回测 | 考虑交易佣金和滑点(成交价偏差)对收益的影响,使回测结果更真实。 |
探索专业框架 | 当策略变复杂时,可转向Backtrader、Zipline或VN.PY等专业量化框架,它们提供了更强大的回测和风险分析工具。 |
您可以从调整均线参数(如short_window
和long_window
)开始,观察策略表现的变化。
**“基准(买入持有)”**是指一种最简单的投资策略:在回测期初全额买入标的资产(如股票或指数),并一直持有到回测期末,期间不做任何主动交易。这个策略通常作为衡量其他复杂策略表现好坏的基准或参照物。
📊 基准策略的核心逻辑与计算
买入持有策略的核心逻辑是相信资产的长期价值增长,避免因频繁交易产生额外成本或错过大幅上涨。在您的代码中,基准收益率的计算方式为:
portfolio_df['benchmark_returns'] = df['close'].pct_change()
这段代码通过计算每日收盘价的百分比变化,模拟了从期初买入并一直持有该资产所能获得的每日收益率序列。其最终资产曲线为:
初始资金 × (期末价格 / 期初价格)
⚖️ 基准策略的比较意义
在量化回测中,设置买入持有作为基准主要有两个目的:
- 衡量策略超额收益:如果您的主动交易策略(如双均线策略)的最终收益超过了同期的买入持有收益,说明策略可能创造了附加价值;反之,如果跑输基准,则策略可能需要优化。
- 评估策略风险调整后收益:有时策略虽然收益略高于基准,但其波动率或最大回撤也更大。通过夏普比率等指标可以比较单位风险带来的收益,判断性价比。
💡 策略对比实例
假设我们回测某只股票从2024年1月1日到2024年10月10日的数据:
指标 | 双均线策略 | 买入持有基准 | 结论 |
---|---|---|---|
总收益率 | +15% | +10% | 策略跑赢基准5个百分点 |
年化波动率 | 25% | 20% | 策略波动更大,风险更高 |
最大回撤 | -12% | -8% | 策略可能遭受的最大亏损更大 |
从这个假设结果可以看出,虽然双均线策略收益更高,但投资者也承担了更大的风险。如果策略收益还不及买入持有,那么这个复杂策略可能就“白折腾”了。
🛠️ 改进基准策略的要点
在实际回测中,一个严谨的买入持有基准还应考虑以下几点,以使比较更公平:
- 初始投入一致:确保策略和基准使用了相同的初始资金。
- 全面考虑成本:基准也应考虑买入和卖出时可能产生的交易费用,尽管它只在期初和期末操作。
- 股息处理:对于股票策略,如果您的主动策略收益包含了股息再投资,那么基准收益的计算也应将股息回报考虑在内。
总之,买入持有基准就像一个标尺,用它来衡量您的交易策略是否真的有效,而不仅仅是靠运气。在您优化策略时,不妨时刻盯着这把标尺。