双均线策略
本文构建了一个完整的量化交易回测框架,包含数据获取、策略逻辑、回测引擎和可视化分析四个核心模块。以双均线策略为例,通过baostock获取股票历史数据,实现短期均线上穿长期均线买入、下穿卖出的策略逻辑。回测模块模拟交易过程,计算持仓和收益,最终输出关键绩效指标并可视化结果。该框架可灵活扩展其他交易策略,为量化交易研究提供基础支持。
构建一个包含数据获取、策略逻辑、回测引擎和结果可视化的完整框架,并使用经典的双均线策略作为示例。
📈 量化交易回测系统代码
数据获取,增加了策略逻辑、回测模拟和可视化分析。
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'] = False
def main():
# 登录Baostock系统
lg = bs.login()
if lg.error_code != '0':
print(f'登录失败: {lg.error_msg}')
return
try:
# 获取股票历史数据
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")
# 将数据转换为DataFrame
data_list = []
while (rs.error_code == '0') & rs.next():
data_list.append(rs.get_row_data())
if not data_list:
print("未获取到数据,请检查股票代码和日期范围。")
return
df = 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'] = 0
df.loc[df['short_ma'] > df['long_ma'], 'signal'] = 1
df.loc[df['short_ma'] < df['long_ma'], 'signal'] = -1
# 计算实际的交易点位(信号发生变化时)
df['positions'] = df['signal'].diff()
# 过滤掉初始的NaN值和信号未变化的点
df['buy_signal'] = (df['positions'] == 2) # 信号从-1或0变为1
df['sell_signal'] = (df['positions'] == -2) # 信号从1或0变为-1
return df
def 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 = 0
current_cash = initial_capital
for 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_price
current_holdings = shares_to_buy
current_cash -= shares_to_buy * current_price
# 信号变为卖出且持有股票时,全仓卖出
elif prev_signal >= 0 and current_signal == -1 and current_holdings > 0:
current_cash += current_holdings * current_price
current_holdings = 0
# 更新持仓数据
df.iloc[i, df.columns.get_loc('holdings')] = current_holdings
df.iloc[i, df.columns.get_loc('cash')] = current_cash
df.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_df
def 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) - 1
benchmark_annual_return = (1 + benchmark_total_return/100) ** (252/days) - 1
# 计算最大回撤
cumulative_max = returns_df['total'].expanding().max()
drawdown = (returns_df['total'] - cumulative_max) / cumulative_max
max_drawdown = drawdown.min() * 100
print("\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% | 策略可能遭受的最大亏损更大 |
从这个假设结果可以看出,虽然双均线策略收益更高,但投资者也承担了更大的风险。如果策略收益还不及买入持有,那么这个复杂策略可能就“白折腾”了。
🛠️ 改进基准策略的要点
在实际回测中,一个严谨的买入持有基准还应考虑以下几点,以使比较更公平:
- 初始投入一致:确保策略和基准使用了相同的初始资金。
- 全面考虑成本:基准也应考虑买入和卖出时可能产生的交易费用,尽管它只在期初和期末操作。
- 股息处理:对于股票策略,如果您的主动策略收益包含了股息再投资,那么基准收益的计算也应将股息回报考虑在内。
总之,买入持有基准就像一个标尺,用它来衡量您的交易策略是否真的有效,而不仅仅是靠运气。在您优化策略时,不妨时刻盯着这把标尺。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)