构建一个包含数据获取、策略逻辑、回测引擎和结果可视化的完整框架,并使用经典的双均线策略作为示例。

📈 量化交易回测系统代码

数据获取,增加了策略逻辑、回测模拟和可视化分析。

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()

💡 代码核心解析

这个代码构成了一个完整的量化回测系统,主要包含以下模块:

  1. 数据获取与预处理:使用Baostock获取原始数据,并转换为Pandas DataFrame,进行数据类型转换和日期索引设置。
  2. 策略逻辑 (dual_moving_average_strategy):实现了经典的双均线策略。当短期均线(如5日)上穿长期均线(如20日)时生成买入信号,下穿时生成卖出信号。
  3. 回测引擎 (backtest_strategy):模拟真实交易,根据策略信号进行买入和卖出操作,并跟踪记录持仓、现金和总资产的变化。
  4. 绩效评估 (print_backtest_results):计算并输出关键绩效指标(KPI),如总收益率、年化收益率、最大回撤等,并与简单的"买入持有"策略进行对比。
  5. 结果可视化 (visualize_backtest_results):将价格走势、交易信号以及策略与基准的资产曲线直观展示出来。
    在这里插入图片描述

🚀 如何优化与进阶

这只是一个基础框架。要让策略更接近实战,可以从以下几个方面深入:

优化方向 具体建议
引入更多数据 添加交易量指标验证信号有效性,或引入大盘指数数据(如沪深300)以控制系统性风险。
完善策略逻辑 增加止损/止盈机制,或结合其他技术指标(如RSI、MACD)进行多重过滤,减少假信号。
精细化回测 考虑交易佣金和滑点(成交价偏差)对收益的影响,使回测结果更真实。
探索专业框架 当策略变复杂时,可转向Backtrader、Zipline或VN.PY等专业量化框架,它们提供了更强大的回测和风险分析工具。

您可以从调整均线参数(如short_windowlong_window)开始,观察策略表现的变化。

**“基准(买入持有)”**是指一种最简单的投资策略:在回测期初全额买入标的资产(如股票或指数),并一直持有到回测期末,期间不做任何主动交易。这个策略通常作为衡量其他复杂策略表现好坏的基准或参照物。

📊 基准策略的核心逻辑与计算

买入持有策略的核心逻辑是相信资产的长期价值增长,避免因频繁交易产生额外成本或错过大幅上涨。在您的代码中,基准收益率的计算方式为:

portfolio_df['benchmark_returns'] = df['close'].pct_change()

这段代码通过计算每日收盘价的百分比变化,模拟了从期初买入并一直持有该资产所能获得的每日收益率序列。其最终资产曲线为:
初始资金 × (期末价格 / 期初价格)

⚖️ 基准策略的比较意义

在量化回测中,设置买入持有作为基准主要有两个目的:

  1. 衡量策略超额收益:如果您的主动交易策略(如双均线策略)的最终收益超过了同期的买入持有收益,说明策略可能创造了附加价值;反之,如果跑输基准,则策略可能需要优化。
  2. 评估策略风险调整后收益:有时策略虽然收益略高于基准,但其波动率或最大回撤也更大。通过夏普比率等指标可以比较单位风险带来的收益,判断性价比。

💡 策略对比实例

假设我们回测某只股票从2024年1月1日到2024年10月10日的数据:

指标 双均线策略 买入持有基准 结论
总收益率 +15% +10% 策略跑赢基准5个百分点
年化波动率 25% 20% 策略波动更大,风险更高
最大回撤 -12% -8% 策略可能遭受的最大亏损更大

从这个假设结果可以看出,虽然双均线策略收益更高,但投资者也承担了更大的风险。如果策略收益还不及买入持有,那么这个复杂策略可能就“白折腾”了。

🛠️ 改进基准策略的要点

在实际回测中,一个严谨的买入持有基准还应考虑以下几点,以使比较更公平:

  • 初始投入一致:确保策略和基准使用了相同的初始资金。
  • 全面考虑成本:基准也应考虑买入和卖出时可能产生的交易费用,尽管它只在期初和期末操作。
  • 股息处理:对于股票策略,如果您的主动策略收益包含了股息再投资,那么基准收益的计算也应将股息回报考虑在内。

总之,买入持有基准就像一个标尺,用它来衡量您的交易策略是否真的有效,而不仅仅是靠运气。在您优化策略时,不妨时刻盯着这把标尺。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐