cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy2)
codes=['600862.SH','300326.SZ','300394.SZ']
#加載最近兩日交易數據
for code in codes:feed = Addmoredata(dataname = get_data(code,'20200506'),name=code)cerebro.adddata(feed)
cerebro.run()
數據查看:
class TestStrategy(bt.Strategy):def __init__(self):# 打印數據集和數據集對應的名稱print("-------------self.datas-------------")print(self.datas)print("-------------self.data-------------")print(self.data._name, self.data) # 返回第一個導入的數據表格,縮寫形式print("-------------self.data0-------------")print(self.data0._name, self.data0) # 返回第一個導入的數據表格,縮寫形式print("-------------self.datas[0]-------------")print(self.datas[0]._name, self.datas[0]) # 返回第一個導入的數據表格,常規形式print("-------------self.datas[1]-------------")print(self.datas[1]._name, self.datas[1]) # 返回第二個導入的數據表格,常規形式print("-------------self.datas[-1]-------------")print(self.datas[-1]._name, self.datas[-1]) # 返回最后一個導入的數據表格print("-------------self.datas[-2]-------------")print(self.datas[-2]._name, self.datas[-2]) # 返回倒數第二個導入的數據表格
data1 = pd.read_csv('111.csv')
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
ed_date = datetime.datetime(2021,1,28)
# 添加 600466.SH 的行情數據
datafeed1 = bt.feeds.PandasData(dataname=data1, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
# 添加 603228.SH 的行情數據
datafeed2 = bt.feeds.PandasData(dataname=data2, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed2, name='603228.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
# 訪問第一個數據集的 close 線
self.data.lines.close # 可省略 lines 簡寫成:self.data.close
self.data.lines_close # 可省略 lines 簡寫成:self.data_close
# 訪問第二個數據集的 open 線
self.data1.lines.close # 可省略 lines 簡寫成:self.data1.close
self.data1.lines_close # 可省略 lines 簡寫成:self.data1_close
# 注:只有從 self.datas 調用 line 時可以省略 lines,調用 indicators 中的 line 時不能省略
獲取數據方法:
如果你能清楚的記住數據表格中每條線的位置,也可以通過索引位置(整數)來訪問,同樣支持簡寫形式:
1、完整形式:self.datas[X].lines[Y];
2、簡寫形式:self.dataX.lines[Y]、self.dataX_Y;
3、說明:X 對應單個數據表格在數據表格集合中的索引位置,Y 對應某條線在數據表格中的索引位置 。
class TestStrategy(bt.Strategy):def __init__(self):print("--------- 打印 self 策略本身的 lines ----------")print(self.lines.getlinealiases())print("--------- 打印 self.datas 第一個數據表格的 lines ----------")print(self.datas[0].lines.getlinealiases())# 計算第一個數據集的s收盤價的20日均線,返回一個 Data feedself.sma = bt.indicators.SimpleMovingAverage(self.datas[0].close, period=20)print("--------- 打印 indicators 對象的 lines ----------")print(self.sma.lines.getlinealiases())print("---------- 直接打印 indicators 對象的所有 lines -------------")print(self.sma.lines) print("---------- 直接打印 indicators 對象的第一條 lines -------------")print(self.sma.lines[0])def next(self):print('驗證索引位置為 6 的線是不是 datetime')print(bt.num2date(self.datas[0].lines[6][0]))# num2date() 作用是將數字形式的時間轉為 date 形式cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
ed_date = datetime.datetime(2021,1,28)
datafeed1 = bt.feeds.PandasData(dataname=data1, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
datafeed2 = bt.feeds.PandasData(dataname=data2, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed2, name='603228.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
1、索引規則:索引位置編號結合了時間信息,
0 號位置永遠指向當前時間點的數據,
-1 號位置指向前一個時間點的數據,
然后依次回退 (backwards)-2、-3、-4、-5、......;
1 號位置指向下一天的數據,然后依次向前(forwards)2、3、4、......;
2、切片方法:get(ago=0, size=1) 函數,
其中 ago 對應數據點的索引位置,即從 ago 時間點開始往前取 size 個數據點。
默認情況下是取當前最新時點(ago=0)的那一個數據(size=1);
3、在編寫策略時,上面提到的對數據點的索引切片操作一般在 next() 函數中涉及較多,
而 __init__() 中涉及較少,
因為__init__() 中一般是對 一整條 line 進行操作(運算)。
class TestStrategy(bt.Strategy):def __init__(self):self.count = 0 # 用于計算 next 的循環次數# 打印數據集和數據集對應的名稱print("------------- init 中的索引位置-------------")print("0 索引:",'datetime',self.data1.lines.datetime.date(0), 'close',self.data1.lines.close[0])print("-1 索引:",'datetime',self.data1.lines.datetime.date(-1),'close', self.data1.lines.close[-1])print("-2 索引",'datetime', self.data1.lines.datetime.date(-2),'close', self.data1.lines.close[-2])print("1 索引:",'datetime',self.data1.lines.datetime.date(1),'close', self.data1.lines.close[1])print("2 索引",'datetime', self.data1.lines.datetime.date(2),'close', self.data1.lines.close[2])print("從 0 開始往前取3天的收盤價:", self.data1.lines.close.get(ago=0, size=3))print("從-1開始往前取3天的收盤價:", self.data1.lines.close.get(ago=-1, size=3))print("從-2開始往前取3天的收盤價:", self.data1.lines.close.get(ago=-2, size=3))print("line的總長度:", self.data1.buflen())def next(self):print(f"------------- next 的第{self.count+1}次循環 --------------")print("當前時點(今日):",'datetime',self.data1.lines.datetime.date(0),'close', self.data1.lines.close[0])print("往前推1天(昨日):",'datetime',self.data1.lines.datetime.date(-1),'close', self.data1.lines.close[-1])print("往前推2天(前日)", 'datetime',self.data1.lines.datetime.date(-2),'close', self.data1.lines.close[-2])print("前日、昨日、今日的收盤價:", self.data1.lines.close.get(ago=0, size=3))print("往后推1天(明日):",'datetime',self.data1.lines.datetime.date(1),'close', self.data1.lines.close[1])print("往后推2天(明后日)", 'datetime',self.data1.lines.datetime.date(2),'close', self.data1.lines.close[2])print("已處理的數據點:", len(self.data1))print("line的總長度:", self.data0.buflen())self.count += 1cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2) # 起始時間
ed_date = datetime.datetime(2021,1,28) # 結束時間
datafeed1 = bt.feeds.PandasData(dataname=data1, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
datafeed2 = bt.feeds.PandasData(dataname=data2, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed2, name='603228.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
__init__() 中:?
訪問的是整條 line,索引編號也是對整條 line 上所有數據點進行編號的,
所以 0 號位置對應導入的行情數據中最晚的那個時間點 2021-01-28,
然后依次 backwards;
1 號位置對應最早的那個時間點 2019-01-02,
然后依次 forwards ;
通過 get() 切片時,如果是從 ago=0 開始取,不會返回數據,從其他索引位置開始取,能返回數據 。
next() 中:
1、由于 next() 是按回測時間點依次循環運行的,
所以 next() 中數據點的索引位置是隨著回測依次推進而動態變化的:backwards 時對應回測過的、已處理過的那部分 line, forwards 時對應還未回測的那部分 line ;
2、在 next() 中,只要記住 0 是當前回測的時間點(今日),
然后站在當前時刻回首過往:-1 是昨日、-2 是前日,依次類推 ;或者站在當前時刻期盼未來:1 是明日、2 是明后日,以此類推 。
獲取 line 長度:
1、self.data0.buflen() 返回整條線的總長度,固定不變;
2、在 next() 中調用 len(self.data0),返回的是當前已處理(已回測)的數據長度,會隨著回測的推進動態增長。
DataFeeds 數據模塊
默認的導入方式
step1:調用 DataFeeds 模塊中的方法讀取數據;
step2:將讀取的數據傳給大腦。
# 讀取和導入 CSV 文件
data = bt.feeds.GenericCSVData(dataname='filename.csv', ...)
cerebro.adddata(data, name='XXX')
# 讀取和導入 dataframe 數據框 - 方式1
data = bt.feeds.PandasData(dataname=df, ...)
cerebro.adddata(data, name='XXX')
# 讀取和導入 dataframe 數據框 - 方式2
data = bt.feeds.PandasDirectData(dataname=df, ...)
cerebro.adddata(data, name='XXX')# 以 GenericCSVData 為例進行參數說明(其他導入函數參數類似)
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 該指標不存在
?Backtrader 中的數據表格默認情況下包含 7 條 line,這 7 條 line 的位置也是固定的,
依次為 ('close', 'low', 'high', 'open', 'volume', 'openinterest', 'datetime') ,
那導入的數據表格必須包含這 7 個指標嗎?指標的排列順序也必須一致嗎?
當然不是!其實你只要告訴 GenericCSVData、PandasData 、PandasDirectData 這 7 個指標在數據源中位于第幾列,如果沒有這個指標,那就將位置設置為 -1?
(如果是dataframe, None 表示指標不存在,-1 表示按位置或名稱自動匹配指標),所以你要做的是讓 Backtrader 知道指標在數據源的哪個位置上 。?
自定義讀取函數
如果你覺得每次都要設置這么多參數來告知指標位置很麻煩,那你也可以重新自定義數據讀取函數,
自定義的方式就是繼承數據加載類 GenericCSVData、PandasData 再構建一個新的類,然后在新的類里統一設置參數:
class My_CSVData(bt.feeds.GenericCSVData):params = (('fromdate', datetime.datetime(2019,1,2)),('todate', datetime.datetime(2021,1,28)),('nullvalue', 0.0),('dtformat', ('%Y-%m-%d')),('datetime', 0),('time', -1),('high', 3),('low', 4),('open', 2),('close', 5),('volume', 6),('openinterest', -1)
)
cerebro = bt.Cerebro()
data = My_CSVData(dataname='daily_price.csv')
cerebro.adddata(data, name='600466.SH')
rasult = cerebro.run()
?新增指標
在回測時,除了常規的高開低收成交量這些行情數據外,還會用到別的指標,
比如選股回測時會用到很多選股因子(PE、PB 、PCF、......),那這些數據又該如何添加進 Backtrader 的數據表格呢?
往 Backtrader 的數據表格里添加指標,就是給數據表格新增列,也就是給數據表格新增 line:
以導入 DataFrame 為例,
在繼承原始的數據讀取類 bt.feeds.PandasData 的基礎上
,設置 lines 屬性和 params 屬性,
新的 line 會按其在 lines 屬性中的順序依次添加進數據表格中,具體對照下面例子的輸出部分:
class PandasData_more(bt.feeds.PandasData):lines = ('pe', 'pb', ) # 要添加的線# 設置 line 在數據源上的列位置params=(('pe', -1),('pb', -1),) # -1表示自動按列明匹配數據,也可以設置為線在數據源中列的位置索引 (('pe',6),('pb',7),)
class TestStrategy(bt.Strategy):def __init__(self):print("--------- 打印 self.datas 第一個數據表格的 lines ----------")print(self.data0.lines.getlinealiases())print("pe line:", self.data0.lines.pe)print("pb line:", self.data0.lines.pb)data1['pe'] = 2 # 給原先的data1新增pe指標(簡單的取值為2)
data1['pb'] = 3 # 給原先的data1新增pb指標(簡單的取值為3)
# 導入的數據 data1 中
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
ed_date = datetime.datetime(2021,1,28)
datafeed1 = PandasData_more(dataname=data1, fromdate=st_date, todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
?
擴展PandasData類,加載更多列數據
#pandas的數據格式
from backtrader.feeds import PandasData
class Addmoredata(PandasData):lines = ('turnover_rate','pe','pb',)params = (('turnover_rate',7),('pe',8),('pb',9),)
擴展GenericCSVData加載csv格式數據
from backtrader.feeds import GenericCSVData
class AddCsvData(GenericCSVData):lines = ('turnover_rate','pe','pb',)params = (('turnover_rate',7),('pe',8),('pb',9),)
import backtrader as bt
from datetime import datetime
class TestStrategy1(bt.Strategy):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(f"換手率:{self.datas[0].turnover_rate[0]},\市凈率:{self.datas[0].pb[0]},市盈率:{self.datas[0].pe[0]}")
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy1)
feed = Addmoredata(dataname = get_data('300002.SZ','20200420'))
#如果是讀取csv數據使用下式
#feed = AddCsvData(dataname = 'test.csv',dtformat=('%Y-%m-%d'))
cerebro.adddata(feed)
cerebro.run()
多只股票數據加載測試:
class TestStrategy2(bt.Strategy):def log(self, txt, dt=None):dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def next(self):for data in self.datas:print(data._name)self.log(f"換手率:{data.turnover_rate[0]},\市凈率:{data.pb[0]},市盈率:{data.pe[0]}")
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy2)
codes=['600862.SH','300326.SZ','300394.SZ']
#加載最近兩日交易數據
for code in codes:feed = Addmoredata(dataname = get_data(code,'20200506'),name=code)cerebro.adddata(feed)
cerebro.run()