1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates import matplotlib.ticker as mtick
# 计算移动平均线 price['short_mavg'] = price['收盘'].rolling(window=20, min_periods=1).mean() price['long_mavg'] = price['收盘'].rolling(window=100, min_periods=1).mean()
# 生成交易信号 price['signal'] = 0.0 price['signal'] = np.where(price['short_mavg'] > price['long_mavg'], 1.0, 0.0)
# 计算每日收益率 price['daily_return'] = price['收盘'].pct_change()
# 计算策略收益 price['strategy_position'] = price['signal'].shift(1) # 使用前一天的信号避免未来函数 price['strategy_return'] = price['strategy_position'] * price['daily_return'] price['strategy_cumulative'] = (1 + price['strategy_return']).cumprod()
# 计算被动持有收益 price['buy_hold_return'] = price['daily_return'] price['buy_hold_cumulative'] = (1 + price['buy_hold_return']).cumprod()
# 计算累计收益 initial_value = 10000 # 假设初始投资10000元 price['strategy_value'] = initial_value * price['strategy_cumulative'] price['buy_hold_value'] = initial_value * price['buy_hold_cumulative']
# 设置绘图风格 plt.style.use('seaborn-v0_8') #指定字体,防止中文出现乱码,windows系统指定为‘SimHei’ plt.rcParams['font.sans-serif'] = ['SimHei'] #这行代码让中文的负号“-”可以正常显示 plt.rcParams["axes.unicode_minus"]=False
# 创建图表 fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 12), dpi=300, sharex=True) fig.suptitle('双均线策略 vs 被动持有收益对比', fontsize=16, fontweight='bold')
# 绘制价格和均线 ax1.plot(price.index, price['收盘'], label='收盘价', lw=1.2, color='black') ax1.plot(price.index, price['short_mavg'], label='20日均线', lw=1.2, color='blue') ax1.plot(price.index, price['long_mavg'], label='100日均线', lw=1.5, color='red') ax1.set_title('价格走势与均线', fontsize=12) ax1.set_ylabel('价格', fontsize=10) ax1.legend(loc='upper left', fontsize=8) ax1.grid(True, alpha=0.4)
# 标记金叉死叉点 gold_cross = price[(price['short_mavg'] > price['long_mavg']) & (price['short_mavg'].shift(1) <= price['long_mavg'].shift(1))] death_cross = price[(price['short_mavg'] < price['long_mavg']) & (price['short_mavg'].shift(1) >= price['long_mavg'].shift(1))]
ax1.scatter(gold_cross.index, gold_cross['short_mavg'], marker='^', s=80, color='green', alpha=0.9, label='金叉(买入)') ax1.scatter(death_cross.index, death_cross['short_mavg'], marker='v', s=80, color='red', alpha=0.9, label='死叉(卖出)')
# 绘制仓位变化 ax2.fill_between(price.index, price['signal'], color='green', alpha=0.3, where=(price['signal'] == 1.0), label='持仓') ax2.fill_between(price.index, price['signal'], color='red', alpha=0.3, where=(price['signal'] == 0.0), label='空仓') ax2.set_title('仓位变化', fontsize=12) ax2.set_ylabel('仓位状态', fontsize=10) ax2.set_yticks([0, 1]) ax2.set_yticklabels(['空仓', '持仓']) ax2.legend(loc='upper left', fontsize=8)
# 绘制收益对比 ax3.plot(price.index, price['strategy_value'], label='双均线策略', lw=1.8, color='darkgreen') ax3.plot(price.index, price['buy_hold_value'], label='被动持有', lw=1.8, color='blue', alpha=0.7) ax3.set_title('收益对比 (初始投资: ¥10,000)', fontsize=12) ax3.set_ylabel('账户价值 (¥)', fontsize=10) ax3.set_xlabel('日期', fontsize=10) ax3.legend(loc='upper left', fontsize=8) ax3.grid(True, alpha=0.4)
# 添加最终收益标注 final_strategy = price['strategy_value'].iloc[-1] final_buy_hold = price['buy_hold_value'].iloc[-1] strategy_return_pct = (final_strategy - initial_value) / initial_value * 100 buy_hold_return_pct = (final_buy_hold - initial_value) / initial_value * 100
ax3.annotate(f'策略最终收益: ¥{final_strategy:,.2f} ({strategy_return_pct:.1f}%)', xy=(price.index[-1], final_strategy), xytext=(price.index[-1] - pd.DateOffset(months=6), final_strategy * 0.8), arrowprops=dict(facecolor='black', shrink=0.05), fontsize=9)
ax3.annotate(f'被动持有收益: ¥{final_buy_hold:,.2f} ({buy_hold_return_pct:.1f}%)', xy=(price.index[-1], final_buy_hold), xytext=(price.index[-1] - pd.DateOffset(months=6), final_buy_hold * 1.2), arrowprops=dict(facecolor='black', shrink=0.05), fontsize=9)
# 设置x轴日期格式 ax3.xaxis.set_major_locator(mdates.MonthLocator(interval=3)) ax3.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) plt.xticks(rotation=45, ha='right')
# 调整布局 plt.tight_layout(rect=[0, 0, 1, 0.96]) # 为总标题留空间 plt.subplots_adjust(hspace=0.2) # 调整子图间距
# 添加统计信息表格 stats_data = [ ["累计收益率", f"{strategy_return_pct:.1f}%", f"{buy_hold_return_pct:.1f}%"], ["年化收益率", f"{(price['strategy_cumulative'].iloc[-1] ** (252/len(price)) - 1) * 100:.1f}%", f"{(price['buy_hold_cumulative'].iloc[-1] ** (252/len(price)) - 1) * 100:.1f}%"], ["最大回撤", f"{((price['strategy_value'].cummax() - price['strategy_value']).max() / initial_value * 100):.1f}%", f"{((price['buy_hold_value'].cummax() - price['buy_hold_value']).max() / initial_value * 100):.1f}%"], ["交易次数", f"{len(gold_cross) + len(death_cross)}次", "-"], ["持仓时间占比", f"{price['signal'].mean() * 100:.1f}%", "100%"] ]
table = plt.table(cellText=stats_data, colLabels=['指标', '双均线策略', '被动持有'], loc='bottom', bbox=[0, -0.5, 1, 0.3])
table.auto_set_font_size(False) table.set_fontsize(10) table.scale(1, 1.5)
# 保存和显示 plt.savefig('收益对比分析.jpg', dpi=300, bbox_inches='tight') plt.show()
|