问题描述
解决方案
在 PTrade 的研究环境(Jupyter Notebook)中,您可以使用 Python 标准库 matplotlib 来绘制策略收益图。
与回测环境自动生成图表不同,在研究环境中,您需要手动获取数据、计算策略逻辑产生的每日收益率,并计算累计净值,最后进行绘图。
以下是详细的步骤和完整的代码示例。
核心步骤
- 获取数据:使用 PTrade 内置的
get_price接口获取标的历史行情。 - 计算指标与信号:编写策略逻辑(例如双均线策略),生成持仓信号。
- 计算收益:根据信号和标的涨跌幅,计算策略的每日收益和累计净值。
- 绘图:使用
matplotlib.pyplot绘制累计收益曲线。
完整代码示例
以下代码演示了一个简单的双均线策略(5日均线上穿20日均线买入,下穿卖出),并将其收益率与基准(买入持有)进行对比绘图。
您可以直接将此代码复制到 PTrade 研究环境的单元格中运行。
import pandas as pd
import matplotlib.pyplot as plt
# 魔法命令,确保图表在 Notebook 中直接显示
%matplotlib inline
# 1. 获取数据
# ----------------------------------------------------------------
stock_code = '600519.SS' # 示例:贵州茅台
start_date = '20220101'
end_date = '20231231'
# 调用 PTrade 内置接口获取收盘价
# 注意:get_price 是 PTrade 环境内置函数,无需 import
df = get_price(stock_code, start_date=start_date, end_date=end_date, fields=['close'])
# 2. 策略逻辑实现 (示例:双均线策略)
# ----------------------------------------------------------------
# 计算均线
df['MA5'] = df['close'].rolling(window=5).mean()
df['MA20'] = df['close'].rolling(window=20).mean()
# 生成信号:MA5 > MA20 时持仓(1),否则空仓(0)
df['signal'] = 0
# 注意:这里使用 loc 进行赋值,避免 SettingWithCopyWarning
df.loc[df['MA5'] > df['MA20'], 'signal'] = 1
# 3. 计算收益率
# ----------------------------------------------------------------
# 计算标的每日涨跌幅
df['pct_change'] = df['close'].pct_change()
# 计算策略每日收益
# 逻辑:今天的信号决定明天的持仓,所以信号需要 shift(1) 下移一天
df['strategy_ret'] = df['signal'].shift(1) * df['pct_change']
# 填充 NaN 值(通常前几天因为计算均线没有数据)
df['strategy_ret'].fillna(0, inplace=True)
df['pct_change'].fillna(0, inplace=True)
# 计算累计净值 (Cumulative Returns)
# 初始资金设为 1
df['strategy_cum'] = (1 + df['strategy_ret']).cumprod()
df['benchmark_cum'] = (1 + df['pct_change']).cumprod()
# 4. 使用 Matplotlib 绘图
# ----------------------------------------------------------------
# 设置画布大小
plt.figure(figsize=(15, 7))
# 绘制基准收益曲线
plt.plot(df.index, df['benchmark_cum'], label='Benchmark (Buy & Hold)', color='gray', linestyle='--', alpha=0.6)
# 绘制策略收益曲线
plt.plot(df.index, df['strategy_cum'], label='Strategy (MA5 vs MA20)', color='red', linewidth=2)
# 设置标题和标签
plt.title('Strategy Backtest Result: {}'.format(stock_code), fontsize=16)
plt.xlabel('Date', fontsize=12)
plt.ylabel('Cumulative Net Value', fontsize=12)
# 添加图例
plt.legend(loc='best')
# 添加网格
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
# 显示图表
plt.show()
代码关键点解析
-
%matplotlib inline:
这是 Jupyter Notebook 的专用命令,必须添加在代码开头,否则生成的图表可能无法直接在网页中显示。 -
df['signal'].shift(1):
非常重要。在回测计算中,如果今天(T日)收盘后计算出买入信号,实际上只能在明天(T+1日)开盘买入,因此享受的是 T+1 日的涨跌幅。如果不进行shift(1),就会引入“未来函数”,导致回测结果虚高。 -
累计净值计算:
使用(1 + daily_return).cumprod()是计算复利收益的标准方法。 -
中文显示问题:
PTrade 的服务器环境通常是 Linux,可能缺失中文字体。如果直接在plt.title中使用中文,可能会显示为方框(乱码)。建议在图表标题和标签中使用英文,或者需要额外配置字体文件(较为繁琐,通常建议直接用英文)。
常见问题 Q&A
Q: 为什么运行代码后报错 NameError: name 'get_price' is not defined?
A: 请确认您是在 PTrade 客户端的“研究”模块中运行代码。get_price 是 PTrade 平台特有的内置函数。如果您是在本地电脑的 Python 环境(如 Anaconda)中运行,是没有这个函数的。
Q: 如何将两条曲线画在两个子图中?
A: 可以使用 plt.subplot。例如:
plt.figure(figsize=(15, 10))
# 上图:净值曲线
plt.subplot(2, 1, 1)
plt.plot(df.index, df['strategy_cum'], color='red')
plt.title('Equity Curve')
plt.grid(True)
# 下图:每日收益波动
plt.subplot(2, 1, 2)
plt.bar(df.index, df['strategy_ret'], color='blue')
plt.title('Daily Returns')
plt.grid(True)
plt.show()
Q: 数据量太大,图表挤在一起怎么办?
A: 可以通过调整 plt.figure(figsize=(宽, 高)) 中的参数来拉长图表。例如设置 figsize=(20, 8) 可以让横轴更宽,时间显示更清晰。