思路:
1.导入需要的包
2.设置变量回测开始时间,股票代码名称
3.定义load_ohlc_from_local读取本地文件600519.csv,设置日期为索引,以DataFrame的格式返还
4.设置变量长期均线,短期均线并计算它们各自的平均值
5.计算做多信号,判断如果短期均线上穿长期均线则产生买入信号1,若短期均线下穿长期均线,则产生卖出信号-1,其他时候为无信号0
6.创建用于标记做多位置的标记,根据信号得到买入和不买入的位置索引
7.绘制入场和出场点,红色三角形为买入点,绿色星号为不买入点,并绘制短期和长期移动平均线
8.计算昨日的持仓状态和当日的收益计算
9.根据买入和卖出信号计算交易成本包含了印花税,滑点,佣金
10.计算年化收益,波动率和夏普比率,用于评估策略表现
11.计算最大回撤找到资产净值曲线最高最低点之间的最大回撤
12.绘制累计收益和最大回撤曲线图
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
bt_start_date = '2018-05-01'
stk_code, stk_name ='sh600519', u'贵州茅台'
def load_ohlc_from_local():
df = pd.read_csv('600519.csv')
df['datetime']=pd.to_datetime(df['date'], format='%Y-%m-%d', errors='coerce')
df =df.set_index('datetime')
return df
smalen = 20 # short
lmalen = 60 # long
dfohlc = load_ohlc_from_local()
dfohlc['SMA'] = dfohlc['close'].rolling(smalen).mean()
dfohlc['LMA'] = dfohlc['close'].rolling(lmalen).mean()
# 调用load_ohlc_from_local()函数加载股票数据,并在数据上计算SMA和LMA
# 计算做多信号
dfohlc['long'] = ((dfohlc['SMA'] > dfohlc['LMA']) &
(dfohlc['SMA'].shift(1) < dfohlc['LMA'].shift(1))
).replace({True: 1, False: 0})
dfohlc['long'] += ((dfohlc['SMA'] < dfohlc['LMA']) &
(dfohlc['SMA'].shift(1) > dfohlc['LMA'].shift(1))
).replace({True: -1, False: 0})
dfohlc['long'].replace({0: np.nan}, inplace=True)
dfohlc['long'].fillna(method='ffill', inplace=True)
dfohlc['long'].replace({-1: 0}, inplace=True)
# 创建用于标记做多位置的标记
plot_start_date = bt_start_date
# plot_start_date = '2018-05-01'
df = dfohlc.loc[plot_start_date:, 'long'].reset_index(drop=True)
idx_long_pos = df[df == 1].index.to_list()
idx_no_pos = df[df == 0].index.to_list()
# 绘制入场和出场点
dfclose = dfohlc.loc[plot_start_date:, 'close']
fig, axes = plt.subplots(2, 1)
ax0, ax1 = axes
ax0.plot(dfclose, color='gray', lw=2.) # lw = line width
ax0.plot(dfclose, '^', markersize=3, color='r', label='Long pos', markevery=idx_long_pos)
ax0.plot(dfclose, '*', markersize=3, color='g', label='No pos', markevery=idx_no_pos)
ax0.plot(dfohlc.loc[plot_start_date:, 'SMA'], color='blue', label='SMA')
ax0.plot(dfohlc.loc[plot_start_date:, 'LMA'], color='purple', label='LMA')
ax0.legend(fontsize=12)
ax0.set_title(stk_name, fontsize=16)
ax0.tick_params(labelsize=12)
ax0.tick_params(axis='x', rotation=30)
ax0.grid()
plt.show()
dfohlc['daily_ret'] = dfohlc['long'].shift(1) * dfohlc['pctChg'] / 100
# 减去交易成本
commission = 0.0003
stamp_duty = 0.001
slippage = 0.001
dfohlc['long'].fillna(0, inplace=True)
dfohlc['buy'] = (dfohlc['long'] - dfohlc['long'].shift(1)) == 1
dfohlc['sell'] = (dfohlc['long'] - dfohlc['long'].shift(1)) == -1
dfohlc['trade_cost'] = dfohlc['buy'] * (commission + slippage)
dfohlc['trade_cost'] += dfohlc['sell'] * \
(commission + slippage + stamp_duty)
dfohlc['daily_ret'] -= dfohlc['trade_cost']
# 计算回测资产净值
dfohlc['equity'] = (1 + dfohlc['daily_ret']).cumprod()
dfohlc.index.name = ''
dfohlc.loc[plot_start_date:, 'equity'].plot(
title ='Backtest Performance',
color='red', ax=ax1)
ax1.grid()
ax1.tick_params(labelsize=12)
plt.tight_layout()
# 计算年化收益率、波动率和夏普比率
rfr = .0 # risk free rate
ann_ret = dfohlc['daily_ret'].mean() * 252
ann_vol = dfohlc['daily_ret'].std() * np.sqrt(252)
Sharpe = (ann_ret - rfr) / ann_vol
print('Sharpe is %.02f.' % Sharpe)
# 计算最大回撤
dfohlc['pre_peak'] = dfohlc['equity'].cummax()
dfohlc['Maximum Drawdown'] = dfohlc['equity'] / dfohlc['pre_peak'] - 1
print('Maximum Drawdown is %.02f%%.' %
(dfohlc['Maximum Drawdown'].min() * 100))
# 绘制累计收益和最大回撤曲线
dfohlc['Cumulative Return'] = dfohlc['equity'] - 1
fig, ax = plt.subplots()
dfohlc.index.name = ''
dfohlc['Maximum Drawdown'].plot(ax=ax)
ax.set_title('Maximum Drawdown', fontsize=16)
ax.grid()
效果: