结构:
创建示例: bt.Cerebro()
喂数据:cerebro.adddata(data)
加策略:cerebro.addstrategy(SmaCross)
运行:cerebro.run()
输出:cerebro.plot()
1. feeds
数据源以feeds形式加到bt里,支持读取本地文件、数据库导入、雅虎文件导入、df导入。
格式:bt.feeds.[PandasData] ()
# 读取和导入 CSV 文件
data = bt.feeds.GenericCSVData(dataname='filename.csv', ...)
# dataframe
data = bt.feeds.PandasData(dataname='filename.csv', ...)
# YahooFinanceData
data = bt.feeds.YahooFinanceData(dataname='MSFT',...)
Backtrader 中的数据表格默认情况下包含 7 条 line,这 7 条 line 的位置也是固定的,
依次为 ('close', 'low', 'high', 'open', 'volume', 'openinterest', 'datetime')
data = bt.feeds.PandasData(dataname=df, datetime=None,
open=0, high=1, low=2, close=3, volume=4, openinterest=-1)
# 或者
data = bt.feeds.YahooFinanceData(dataname='MSFT',
fromdate=datetime(2011, 1, 1),
todate=datetime(2012, 12, 31))
# 以 GenericCSVData 为例进行参数说明(其他导入函数参数类似)
data = bt.feeds.GenericCSVData(
dataname='daily_price.csv', # 数据源,CSV文件名 或 Dataframe对象
fromdate=st_date, # 读取的起始时间
todate=ed_date, # 读取的结束时间
nullvalue=0.0, # 缺失值填充
dtformat=('%Y-%m-%d'), # 日期解析的格式
# 下面是数据表格默认包含的 7 个指标,取值对应指标在 daily_price.csv 中的列索引位置
datetime=0, # 告诉 GenericCSVData, datetime 在 daily_price.csv 文件的第1列
high=3,
low=4,
open=2,
close=5,
volume=6,
openinterest=-1) # 如果取值为 -1 , 告诉 GenericCSVData 该指标不存在
目前一个完整的喂数据如下:
import backtrader as bt
cerebro = bt.Cerebro()
stock_hfq_df = pd.read_excel("./data/sh600000.xlsx", index_col='date', parse_dates=True)
data = bt.feeds.PandasData(dataname=stock_hfq_df, fromdate=start_date, todate=end_date) # 加载数据
cerebro.adddata(data) # 将数据传入回测系统
导入的数据表格不一定非要含有上述7个指标,没有的指标标记为-1,也可能不止上述几个指标,比如增加pe,pb等。可重新自定义读取函数
class Addmoredata(PandasData):
lines = ('news','heat','turnover_rate_f','circ_mv','total_mv',)
params = (('news',7),('heat',8),('turnover_rate_f',9),('circ_mv',10),('total_mv',11),)
data = Addmoredata(dataname=df)
cerebro.adddata(data) # 将数据传入回测系统
多股票加入:
datafeed1 = bt.feeds.PandasData(dataname=data1)
cerebro.adddata(datafeed1, name='600466.SH')
datafeed2 = bt.feeds.PandasData(dataname=data2)
cerebro.adddata(datafeed2, name='600466.SH')
2.加策略
class 策略名(bt.Stragety):
pass
cerebro.addstrategy(策略名)
2.1 日志
def log(self, txt, dt=None):
''' 日志函数 '''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def next(self):
# 记录数据序列的收盘价
self.log('收盘价, %.2f' % (self.dataclose[0]))
log()表示调用提供的日期和txt变量
这里多个股票怎么表示,怎么选取,还没找到答案,先待定。
2.2 init初始函数
重要!
主要为对变量的定义,包括收盘价,自设指标,自设信号等
params = (
('maperiod', 15),
)
def __init__(self):
# 定义收盘价
self.dataclose = self.datas[0].close
# 定义状态,价格,佣金
self.order = None
self.buyprice = None
self.buycomm = None
# 增加移动平均指标
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
# 增加划线的指标
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
# 增加划线指标2
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
# 自设信号:生成是个line
# data0 是日线数据
sma0 = btind.SMA(self.data0, period=15) # 15 天的平均
# data1 是周线数据
sma1 = btind.SMA(self.data1, period=5) # 5 周的平均
self.buysig = sma0 > sma1()
#自设信号:用bt.And来表示同时取信号
sma = btind.SimpleMovinAverage(self.data, period=20)
close_over_sma = self.data.close > sma
sma_dist_to_high = self.data.high - sma
sma_dist_small = sma_dist_to_high < 3.5
# 注意and在python中不能重写,所以只能通过函数实现。
sell_sig = bt.And(close_over_sma, sma_dist_small)
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15)
#如果sma1大于close,那么返回数据源的low line,否则返回high line。特别注意的是在这里并没有返回实际有效的值,只是返回lines对象,类似我们之前使用SimpleMovingAverage
high_or_low = bt.If(sma1 > self.data.close, self.data.low, self.data.high)
sma2 = btind.SMA(high_or_low, period=15)
多股的自设指标
MIN_PERIOD = 20
# 可配置策略参数
params = dict(
period = MIN_PERIOD, # 均线周期
stake = 100, # 单笔交易股票数目
)
def __init__(self):
self.inds = dict()
for i, d in enumerate(self.datas):
self.inds[d] = bt.ind.SMA(d.close, period=self.p.period)
2.3 notify
主要为记录订单状态和交易状态
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Check if an order has been completed
# Attention: broker could reject order if not enough cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Write down: no pending order
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
2.4 next
重要!!
进入买入卖出操作,此部分内置函数较多
还可以增加pre_next
def next(self):
# 在这里,操作符用于判决。
if self.sma > 30.0:
print('sma is greater than 30.0')
if self.sma > self.data.close:
print('sma is above the close price')
if self.sell_sig: # if sell_sig == True: 这种写法也可以
print('sell sig is True')
else:
print('sell sig is False')
2.4.1 单只股票的买卖操作
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])
# 检查是否有未完成的交易单,有则不挂新单。
if self.order:
return
# 检查是否在场中(有头寸),有则不买入
if not self.position:
# 买入逻辑
if self.dataclose[0] < self.dataclose[-1]:
# current close less than previous close
if self.dataclose[-1] < self.dataclose[-2]:
# previous close less than the previous close
# BUY, BUY, BUY!!! (with default parameters)
self.log('BUY CREATE, %.2f' % self.dataclose[0])
# 跟踪当前交易单,避免重复下单。
self.order = self.buy()
else:
# 有头寸,考虑卖出
if len(self) >= (self.bar_executed + 5):
# 当前据买入日期过去5bar,执行卖出
self.log('SELL CREATE, %.2f' % self.dataclose[0])
# 跟踪卖单
self.order = self.sell()
plus版本
def next(self):
if self.order:
return
# 入场:价格突破上轨线且空仓时
if self.buy_signal > 0 and self.buy_count == 0:
atr = self.ATR[0]
self.buy_size = self.broker.getvalue() * 0.01 / self.ATR
self.buy_size = int(self.buy_size / 100) * 100
self.sizer.p.stake = self.buy_size # 指定买卖多少股。
print('ATR', atr, 'buy_size', self.sizer.p.stake, 'close', self.data.close[0], '市值',
self.broker.getvalue() * 0.01)
self.buy_count = 1
# print('空仓时买入', self.buy_size)
self.order = self.buy()
# 加仓:价格上涨了买入价的0.5的ATR且加仓次数少于3次(含)
elif self.data.close > self.buyprice + 0.5 * self.ATR[0] and 0 < self.buy_count <= 4:
# print('加仓买入')
self.buy_size = self.broker.getvalue() * 0.01 / self.ATR
self.buy_size = int(self.buy_size / 100) * 100
self.sizer.p.stake = self.buy_size
self.order = self.buy()
self.buy_count += 1
# 离场:价格跌破下轨线且持仓时
elif self.sell_signal < 0 and self.buy_count > 0:
# print('平仓信号卖出')
self.order = self.sell()
self.buy_count = 0
# 止损:价格跌破买入价的2个ATR且持仓时
elif self.data.close < (self.buyprice - 2 * self.ATR[0]) and self.buy_count > 0:
# print('止损信号卖出')
self.order = self.sell()
self.buy_count = 0
常见的函数
# 当前nav
self.broker.getvalue()
#下单
self.order_target_percent(secu_data, target_pct, name=secu)
self.order_target_value(secu_data, target_val, name=secu)
self.buy(secu_data, order_amount, name=secu) # code, num, name
self.sell(secu_data, order_amount, name=secu)
# 清仓
self.order = self.close(secu_data)
# 仓位
self.getposition().size
# 日期
self.datetime.date()
2.4.2 多只股票的买卖操作
# 获取名字
self.getdatabyname('300015')._name
# 对应收盘
self.getdatabyname('300015').close[0]
self.getdatanames()按顺序返回所有股票的名称list
self.getdatabyname(secu_name):返回该股票的data
def next(self):
if self.order: # 检查是否有指令等待执行
return
# 如果是最后一天,不进行买卖
if pd.Timestamp(self.datas[0].datetime.date(0)) == end_dates[self.datas[0]._name]:
return
# 是否持仓
if len(self.buy_list) < 2: # 没有持仓
# 没有购买的票
for secu in set(self.getdatanames()) - set(self.buy_list):
data = self.getdatabyname(secu)
# 如果突破 20 日均线买买买,不要在最后一根bar的前一天买
if data.close > self.sma[secu] and pd.Timestamp(data.datetime.date(1)) < end_dates[secu]:
# 买买买
order_value = self.broker.getvalue() * 0.48
order_amount = self.downcast(order_value / data.close[0], 100)
self.order = self.buy(data, size=order_amount, name=secu)
self.log(f"买{secu}, price:{data.close[0]:.2f}, amout: {order_amount}")
self.buy_list.append(secu)
elif self.position:
now_lst = []
for secu in self.buy_list:
data = self.getdatabyname(secu)
# 执行卖出条件判断:收盘价格跌破20日均线,或者股票最后一根bar 的前一天之剔除日
if data.close[0] < self.sma[secu] or pd.Timestamp(data.datetime.date(1)) >= end_dates[secu]:
# 卖卖卖
self.order = self.order_target_percent(data, 0, name=secu)
self.log(f"卖{secu}, price:{data.close[0]:.2f}, pct: 0")
continue
now_lst.append(secu)
self.buy_list = now_lst
多只股票有定时和不定时处理,定时如每月1号按照股息率排序买入,不定时处理每日判断进行调仓。
定时调仓,需要增加定时器