backtrader的基本策略构成:
#构成
#Backtrader 回测代码编写流程如下: import backtrader as bt # 导入 Backtrader
import backtrader.indicators as btind # 导入策略分析模块
import backtrader.feeds as btfeeds # 导入数据模块# 创建策略
class TestStrategy(bt.Strategy):# 可选,设置回测的可变参数:如移动均线的周期params = ((...,...), # 最后一个“,”最好别删!)def log(self, txt, dt=None):'''可选,构建策略打印日志的函数:可用于打印订单记录或交易记录等'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):'''必选,初始化属性、计算指标等'''passdef notify_order(self, order):'''可选,打印订单信息'''passdef notify_trade(self, trade):'''可选,打印交易信息'''passdef next(self):'''必选,编写交易策略逻辑'''sma = btind.SimpleMovingAverage(...) # 计算均线pass# 实例化 cerebro
cerebro = bt.Cerebro()
# 通过 feeds 读取数据
data = btfeeds.BacktraderCSVData(...)
# 将数据传递给 “大脑”
cerebro.adddata(data)
# 通过经纪商设置初始资金
cerebro.broker.setcash(...)
# 设置单笔交易的数量
cerebro.addsizer(...)
# 设置交易佣金
cerebro.broker.setcommission(...)
# 添加策略
cerebro.addstrategy(TestStrategy)
# 添加策略分析指标
cerebro.addanalyzer(...)
# 添加观测器
cerebro.addobserver(...)
# 启动回测
cerebro.run()
# 可视化回测结果
cerebro.plot()
数据传入必须包含的字段:
datetime sec_code open high low close volume openinterest
0 2019-01-02 600466.SH 33.064891 33.496709 31.954503 32.386321 10629352 0
1 2019-01-02 603228.SH 50.660230 51.458513 50.394136 51.120778 426147 0
3、导入 backtrader,构建“大脑”
import backtrader as bt # 导入 Backtrader # 实例化 cerebro
cerebro = bt.Cerebro()
获取当前资金
cerebro.broker.getvalue()
4、如何导入多只股票的历史行情数据?
# 按股票代码,依次循环传入数据
for stock in daily_price['sec_code'].unique():# 日期对齐data = pd.DataFrame(index=daily_price.index.unique()) # 获取回测区间内所有交易日df = daily_price.query(f"sec_code=='{stock}'")[['open','high','low','close','volume','openinterest']]data_ = pd.merge(data, df, left_index=True, right_index=True, how='left')# 缺失值处理:日期对齐时会使得有些交易日的数据为空,所以需要对缺失数据进行填充data_.loc[:,['volume','openinterest']] = data_.loc[:,['volume','openinterest']].fillna(0)data_.loc[:,['open','high','low','close']] = data_.loc[:,['open','high','low','close']].fillna(method='pad')data_.loc[:,['open','high','low','close']] = data_.loc[:,['open','high','low','close']].fillna(0)# 导入数据datafeed = bt.feeds.PandasData(dataname=data_, fromdate=datetime.datetime(2019,1,2), todate=datetime.datetime(2021,1,28))cerebro.adddata(datafeed, name=stock) # 通过 name 实现数据集与股票的一一对应print(f"{stock} Done !")
5、配置回测条件(手续费,滑点,初始资金)
# 初始资金 100,000,000
cerebro.broker.setcash(100000000.0)
# 佣金,双边各 0.0003
cerebro.broker.setcommission(commission=0.0003)
# 滑点:双边各 0.0001
cerebro.broker.set_slippage_perc(perc=0.0001)
6、查看指标
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='pnl') # 返回收益率时序数据
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='_AnnualReturn') # 年化收益率
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='_SharpeRatio') # 夏普比率
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='_DrawDown') # 回撤
7、交易代码
self.close() 平仓;
self.buy() 买入、做多;
self.sell() 卖出、做空;
self.cancel() 取消订单;
self.order_target_percent() 按持仓百分比下单,“多退少补”原则, 对于股票当前无持仓或持有的是多单(size>=0)的情况,若目标占比 target > 当前持仓占比,买入不够的部分;若目标占比 target < 当前持仓占比,卖出多余的部分。
8、打印回测日志
def notify_order(self, order):# 未被处理的订单if order.status in [order.Submitted, order.Accepted]:return# 已经处理的订单if order.status in [order.Completed, order.Canceled, order.Margin]:if order.isbuy():self.log('BUY EXECUTED, ref:%.0f,Price: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %(order.ref, # 订单编号order.executed.price, # 成交价order.executed.value, # 成交额order.executed.comm, # 佣金order.executed.size, # 成交量order.data._name)) # 股票名称else: # Sellself.log('SELL EXECUTED, ref:%.0f, Price: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %(order.ref,order.executed.price,order.executed.value,order.executed.comm,order.executed.size,order.data._name))
9、打印回测结果
# 启动回测
result = cerebro.run()
# 从返回的 result 中提取回测结果
strat = result[0]
# 返回日度收益率序列
daily_return = pd.Series(strat.analyzers.pnl.get_analysis())
# 打印评价指标
print("--------------- AnnualReturn -----------------")
print(strat.analyzers._AnnualReturn.get_analysis())
print("--------------- SharpeRatio -----------------")
print(strat.analyzers._SharpeRatio.get_analysis())
print("--------------- DrawDown -----------------")
print(strat.analyzers._DrawDown.get_analysis())