#!/usr/bin/env python
coding: utf-8
In[1]:
import backtrader as bt
import pandas as pd
import numpy as np
import datetime
from copy import deepcopy
# 小试一下
In[2]:
实例化 cerebro
cerebro = bt.Cerebro()
打印初始资金
print(‘Starting Portfolio Value: %.2f’ % cerebro.broker.getvalue())
启动回测
cerebro.run()
打印回测完成后的资金
print(‘Final Portfolio Value: %.2f’ % cerebro.broker.getvalue())
# 一、数据准备
## 1.1 读取日度行情表
表内字段就是 Backtrader 默认情况下要求输入的 7 个字段: ‘datetime’ 、‘open’、‘high’、‘low’、‘close’、‘volume’、‘openinterest’,外加一个 ‘sec_code’ 股票代码字段。
In[3]:
daily_price = pd.read_csv(“./data/daily_price.csv”, parse_dates=[‘datetime’])
daily_price
In[4]:
daily_price.query(“sec_code==‘600466.SH’”)
In[5]:
以 datetime 为 index,类型为 datetime 或 date 类型,Datafeeds 默认情况下是将 index 匹配给 datetime 字段;
daily_price = daily_price.set_index([‘datetime’])
## 1.2 读取调仓信息表
表内数据说明:
+ trade_date: 调仓期(每月最后一个交易日);
+ sec_code:持仓成分股;
+ weight:持仓权重。
In[6]:
trade_info = pd.read_csv(“./data/trade_info.csv”, parse_dates=[‘trade_date’])
trade_info
# 二、 选股回测
选股策略:定期按持仓权重调仓 。
In[7]:
回测策略
class TestStrategy(bt.Strategy):
params = (
(‘buy_stocks’, None), # 传入各个调仓日的股票列表和相应的权重
)
def log(self, txt, dt=None):
‘’’ Logging function fot this strategy’‘’
dt = dt or self.datas[0].datetime.date(0)
print(‘{}, {}’.format(dt.isoformat(), txt))
def __init__(self):
# 读取调仓日期,即每月的最后一个交易日,回测时,会在这一天下单,然后在下一个交易日,以开盘价买入
self.trade_dates = pd.to_datetime(self.p.buy_stocks['trade_date'].unique()).tolist()
self.buy_stock = self.p.buy_stocks # 保留调仓信息
self.order_list = [] # 记录以往订单,在调仓日要全部取消未成交的订单
self.buy_stocks_pre = [] # 记录上一期持仓
def next(self):
# 获取当前的回测时间点
dt = self.datas[0].datetime.date(0)
# 打印当前时刻的总资产
self.log('当前总资产 %.2f' %(self.broker.getvalue()))
# 如果是调仓日,则进行调仓操作
if dt in self.trade_dates:
print("--------------{} 为调仓日----------".format(dt))
#取消之前所下的没成交也未到期的订单
if len(self.order_list) > 0:
print("--------------- 撤销未完成的订单 -----------------")
for od in self.order_list:
# 如果订单未完成,则撤销订单
self.cancel(od)
#重置订单列表
self.order_list = []
# 提取当前调仓日的持仓列表
buy_stocks_data = self.buy_stock.query(f"trade_date=='{dt}'")
long_list = buy_stocks_data['sec_code'].tolist()
print('long_list', long_list) # 打印持仓列表
# 对现有持仓中,调仓后不再继续持有的股票进行卖出平仓
sell_stock = [i for i in self.buy_stocks_pre if i not in long_list]
print('sell_stock', sell_stock)
if len(sell_stock) > 0:
print("-----------对不再持有的股票进行平仓--------------")
for stock in sell_stock:
data = self.getdatabyname(stock)
if self.getposition(data).size > 0 :
od = self.close(data=data)
self.order_list.append(od) # 记录卖出订单
# 买入此次调仓的股票:多退少补原则
print("-----------买入此次调仓期的股票--------------")
for stock in long_list:
w = buy_stocks_data.query(f"sec_code=='{stock}'")['weight'].iloc[0] # 提取持仓权重
data = self.getdatabyname(stock)
order = self.order_target_percent(data=data, target=w*0.95) # 为减少可用资金不足的情况,留 5% 的现金做备用
self.order_list.append(order)
self.buy_stocks_pre = long_list # 保存此次调仓的股票列表
#订单日志
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: # Sell
self.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))
In[8]:
实例化大脑
cerebro_ = bt.Cerebro()
按股票代码,依次循环传入数据
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)
print(f”{stock} Done !")
In[9]:
cerebro = deepcopy(cerebro_) # 深度复制已经导入数据的 cerebro_,避免重复导入数据
初始资金 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)
添加策略
cerebro.addstrategy(TestStrategy, buy_stocks=trade_info) # 通过修改参数 buy_stocks ,使用同一策略回测不同的持仓列表
添加分析器
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name=‘pnl’) # 返回收益率时序数据
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name=‘_AnnualReturn’)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.003, annualize=True, _name=‘_SharpeRatio’)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name=‘_DrawDown’)
添加观测器
cerebro.addobserver(bt.observers.Value) # 查看账户资产变动
启动回测
result = cerebro.run()
In[10]:
strat = result[0]
print(“--------------- AnnualReturn -----------------”)
print(strat.analyzers._AnnualReturn.get_analysis())
print(“--------------- SharpeRatio -----------------”)
print(strat.analyzers._SharpeRatio.get_analysis())
print(“--------------- DrawDown -----------------”)
print(strat.analyzers._DrawDown.get_analysis())