上一篇文章讲到了均线的指标的运用,策略是比较简单的策略,就是使用5日均线和10均线的价格做比较,然后进行全仓的买卖,本文会继续沿用上文所使用的策略。不同的是,本文笔者会较为详细的介绍,编写策略过程中,数据的获取方式,比如指标的定义方式,backtrader而言,指标有两种方式,一种是backtrader本身就支持的定义好的指标,比如5日或者10均线,具体backtrader支持了多少指标后续笔者会为大家继续整理,另一种就是自定义指标。
以编写5日均线对比10日均线而言,编写策略中所需要的常用数据做个简单的介绍:
# 初始化时
设置初始资金,cerebro.broker.setcash( 100000000.0)
# 策略中调用
获取当前可用资金,cerebro.broker.getcash()
self.dataclose[0] # 当日的收盘价
self.dataclose[-1] # 昨天的收盘价
self.dataclose[-2] # 前天的收盘价
当前总资产self.broker.getvalue
当前持仓量, self.getposition(self.data).size
当前持仓成本, self.getposition(self.data).price
Broker 在每次交易后更新 cash 外,还会同时更新当前总资产 value 和当前持仓 position,通常在 Strategy 中调用上述方法进行查询
指标数据使用:
均线是backtrader本身就支持的指标:
# 策略参数
params = dict(
period5=5, # 5均线周期
period10=10, # 10均线周期
look_back_days=30,
printlog=False
)
self.mas[data._id] = bt.ind.SMA(data.close, period=self.p.period5) #5日均线
self.mas10[data._id] = bt.ind.SMA(data.close, period=self.p.period10) #10日均线
其中bt.ind.SMA是backtrader就已经定义好的均线的获取方式,backtrader支持的更多的指标请读者自行在backtrader查看,关于如果自定义指标,后续笔者会继续为大家整理。
通过上述的数据就能够获取指标,然后结合之前的策略构建步骤,就能实现策略:5日均线高于10均,全仓买入,反之全仓卖出。
代码如下:
import tushare as ts
import pandas as pd
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
# Import the backtrader platform
import backtrader as bt
class MyStrategy(bt.Strategy):
# 策略参数
params = dict(
period5=5, # 5均线周期
period10=10, # 10均线周期
look_back_days=30,
printlog=False
)
def __init__(self):
self.mas = dict()
self.mas10 = dict()
# 遍历所有股票,计算20日均线
for data in self.datas:
self.mas[data._id] = bt.ind.SMA(data.close, period=self.p.period5)
self.mas10[data._id] = bt.ind.SMA(data.close, period=self.p.period10)
def next(self):
# 得到当前的账户价值
total_value = self.broker.getcash()
print('剩余的资金->' + str(total_value))
for data in self.datas:
# 获取仓位
pos = self.getposition(data).size
if self.mas10[data._id][0] < self.mas[data._id][0]:
p_value = total_value * 0.9 / 10
size = ((int(total_value / self.data.close[0]))) - ((int(total_value / self.data.close[0])) % 100) - 100
print('10日均线价格->' + str(self.mas10[data._id][0]) + ',5日均线价格->' + str(self.mas[data._id][0]) + \
',持仓数量->' + str(pos) + ',买入数量->' + str(size))
self.buy(data=data, size=size)
if pos > 0 and self.mas10[data._id][0] > self.mas[data._id][0]:
print('10日均线价格->' + str(self.mas10[data._id][0]) + ',5日均线价格->' + str(self.mas[data._id][0]) + \
',持仓数量->' + str(pos) + ',卖出数量->' + str(pos))
self.order = self.sell(data=data, size=pos)
def log(self, txt, dt=None, doprint=False):
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print(f'{dt.isoformat()},{txt}')
# 记录交易执行情况(可省略,默认不输出结果)
def notify_order(self, order):
# 如果order为submitted/accepted,返回空
if order.status in [order.Submitted, order.Accepted]:
return
# 如果order为buy/sell executed,报告价格结果
if order.status in [order.Completed]:
if order.isbuy():
self.log(f'买入:\n价格:{order.executed.price:.2f},\
成本:{order.executed.value:.2f},\
数量:{order.executed.size:.2f},\
手续费:{order.executed.comm:.2f}')
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log(f'卖出:\n价格:{order.executed.price:.2f},\
成本: {order.executed.value:.2f},\
数量:{order.executed.size:.2f},\
手续费{order.executed.comm:.2f}')
self.bar_executed = len(self)
# 如果指令取消/交易失败, 报告结果
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('交易失败')
self.order = None
# 记录交易收益情况(可省略,默认不输出结果)
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log(f'策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}')
pro = ts.pro_api('cbb257058b7cb228769b4949437c27c27e5132e882747dc80f01a5a5')
def ts_get_daily_stock(code, start_dt, end_dt):
start_dt = start_dt.replace("'", "", 3);
end_dt = end_dt.replace("'", "", 3);
# start_dt = '20190101'
# end_dt=''
print(code, start_dt, end_dt)
data = pro.daily(ts_code=code, start_date=start_dt, end_date=end_dt)
data['trade_date'] = pd.to_datetime(data['trade_date'])
data['trade_date'] = pd.to_datetime(data['trade_date'])
data = data.sort_values(by='trade_date')
data.index = data['trade_date']
data['openinterest'] = 0
data['volume'] = data['vol']
data = data[
['open', 'close', 'high', 'low', 'volume']
]
return data
# 读取选股的结果
df = pd.read_csv('stock_alpha.csv')
df.columns = ['ts_code', 'name', 'alpha', 'start_dt', 'end_dt']
min_a = df.sort_values(by='alpha')
min_a = min_a.iloc[:10, :]
code = []
code = min_a['ts_code'] # 股票代码
start_dts = []
start_dts = min_a['start_dt'] # 股票代码起始时间
end_dts = []
end_dts = min_a['end_dt'] # 股票代码结束时间
for i in range(len(code)):
data = ts_get_daily_stock(code.iloc[i], start_dts.iloc[i], end_dts.iloc[i]) # 字段分别为股票代码、开始日期、结束日期
data.to_csv(code.iloc[i] + '.csv')
cerebro = bt.Cerebro()
for i in range(len(code)): # 循环获取股票历史数据
dataframe = pd.read_csv(code.iloc[i] + '.csv', index_col=0, parse_dates=True)
dataframe['openinterest'] = 0
data = bt.feeds.PandasData(dataname=dataframe,
fromdate=datetime.datetime(2006, 8, 18),
todate=datetime.datetime(2022, 3, 22)
)
cerebro.adddata(data)
# 回测设置
startcash = 100000.0
cerebro.broker.setcash(startcash)
# 设置佣金为千分之一
cerebro.broker.setcommission(commission=0.001)
# 添加策略
cerebro.addstrategy(MyStrategy, printlog=True)
cerebro.run()
# 获取回测结束后的总资金
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash
# 打印结果
print(f'总资金: {round(portvalue,2)}')
print(f'净收益: {round(pnl,2)}')
cerebro.plot()
其中用到的csv文件还是从上一章的地址获取
结果为:
总资金: 136632.34
净收益: 36632.34
执行图为: