提示词

1
2
3
4
5
6
7
8
9
10
price = pd.read_excel('../数据/601001策略回测实验数据.xlsx',
index_col='日期')

# 将index转换成datetime格式
price.index = pd.to_datetime(price.index)

# 保留收盘价
price = price[['收盘']]

在名叫price的DataFrame中,保存了某只股票的每日收盘价。现在需要基于长短期均线生成交易信号,具体为当20日均线上穿100日均线时买入,否则卖出,最后将收盘价、长短期均线和根据交易信号发生的仓位变化进行可视化,仓位变化需要以子图的形式显示在下方,请给出示例代码。

答案

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
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator

# 计算移动平均线
price['MA20'] = price['收盘'].rolling(window=20).mean() # 20日均线
price['MA100'] = price['收盘'].rolling(window=100).mean() # 100日均线

# 生成交易信号 (1:买入, 0:卖出)
price['Signal'] = np.where(price['MA20'] > price['MA100'], 1, 0)

# 计算仓位变化 (当信号从0变1时买入,从1变0时卖出)
price['Position'] = price['Signal'].diff()

# 创建可视化图表
plt.figure(figsize=(14, 10))
plt.suptitle('双均线交易策略 (20日 vs 100日)', fontsize=16, fontweight='bold')

# 价格和均线图 (主图)
ax1 = plt.subplot(2, 1, 1)
ax1.plot(price.index, price['收盘'], label='收盘价', color='black', linewidth=1.5, alpha=0.8)
ax1.plot(price.index, price['MA20'], label='20日均线', color='blue', linewidth=1.5)
ax1.plot(price.index, price['MA100'], label='100日均线', color='red', linewidth=2)

# 标记买入卖出信号
buy_signals = price[price['Position'] == 1]
sell_signals = price[price['Position'] == -1]
ax1.scatter(buy_signals.index, buy_signals['收盘'],
marker='^', s=80, color='green', alpha=0.9, label='买入信号')
ax1.scatter(sell_signals.index, sell_signals['收盘'],
marker='v', s=80, color='red', alpha=0.9, label='卖出信号')

# 填充均线之间的区域
ax1.fill_between(price.index, price['MA20'], price['MA100'],
where=price['MA20'] >= price['MA100'],
facecolor='lightgreen', alpha=0.3, interpolate=True)
ax1.fill_between(price.index, price['MA20'], price['MA100'],
where=price['MA20'] < price['MA100'],
facecolor='lightcoral', alpha=0.3, interpolate=True)

ax1.set_title('价格走势与均线交叉信号', fontsize=14)
ax1.set_ylabel('价格', fontsize=12)
ax1.legend(loc='upper left')
ax1.grid(True, linestyle='--', alpha=0.6)

# 仓位变化图 (子图)
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
ax2.plot(price.index, price['Signal'],
label='持仓状态', color='purple', linewidth=2)
ax2.fill_between(price.index, 0, price['Signal'],
where=price['Signal'] >= 0,
facecolor='purple', alpha=0.3)

# 标记仓位变化点
ax2.scatter(buy_signals.index, np.ones(len(buy_signals)),
marker='^', s=80, color='green', alpha=0.9, label='建仓点')
ax2.scatter(sell_signals.index, np.zeros(len(sell_signals)),
marker='v', s=80, color='red', alpha=0.9, label='平仓点')

ax2.set_title('仓位变化', fontsize=14)
ax2.set_ylabel('仓位状态\n(1=持有, 0=空仓)', fontsize=12)
ax2.set_xlabel('日期', fontsize=12)
ax2.legend(loc='upper left')
ax2.yaxis.set_major_locator(MaxNLocator(integer=True)) # Y轴只显示整数
ax2.grid(True, linestyle='--', alpha=0.6)

# 添加策略说明
stats_text = f"策略统计:\n- 总买入信号: {len(buy_signals)}\n- 总卖出信号: {len(sell_signals)}\n- 最大持仓天数: {price['Signal'].sum()}"
plt.figtext(0.75, 0.25, stats_text, bbox=dict(facecolor='white', alpha=0.5), fontsize=10)

plt.tight_layout(rect=[0, 0, 1, 0.96]) # 为标题留出空间
plt.savefig('双均线交易策略可视化.png', dpi=300, bbox_inches='tight')
plt.show()

# 输出交易信号详情
print("交易信号详情:")
print(price[['收盘', 'MA20', 'MA100', 'Signal', 'Position']].dropna().tail(10))

结果:

计算交易策略的收益

简化代码加描述

提示词

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
# Python代码
# 计算20日均线和100日均线
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'][price['short_mavg'] > price['long_mavg']] = 1.0
# 绘制收盘价和均线
plt.figure(dpi=300)

plt.subplot(2, 1, 1)
plt.plot(price['收盘'], label='收盘价',lw=1)
plt.plot(price['short_mavg'], label='20日均线',lw=1)
plt.plot(price['long_mavg'], label='100日均线',lw=1)
plt.title('收盘价和均线')
plt.legend()

# 绘制仓位变化
plt.subplot(2, 1, 2)
plt.fill_between(price.index, price['signal'],
color='green', #alpha=0.5,
where=(price['signal'] == 1.0))
plt.fill_between(price.index, price['signal'],
color='red', #alpha=0.5,
where=(price['signal'] == 0.0))
plt.title('仓位变化')
plt.tight_layout()
plt.savefig('图12-1.jpg', dpi=300)
plt.show()

基于上面的代码,将持仓的收益与该股票被动持有的收益进行对比,并将对比结果可视化,请给出示例代码。

答案

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

结果:

交易策略最后收益还不如一直持有,要调整。