我們之前在喂歷史資料,都是先用 shioaji 下載下來,然後再用 padas 轉成 dataframe,最後再喂給 backtrader,在 backtrader 裡的 datafeeds 也是可以自訂義的,有幾個步驟:
params = (('dataname', None),
('fromdate', datetime.datetime.min),
('todate', datetime.datetime.max),
('name', ''),
('compression', 1),
('timeframe', TimeFrame.Days),
('sessionend', None))
以下我就用 Shioaji 取得資料來示範
import backtrader as bt
import backtrader.feeds as btfeeds
from backtrader import date2num
import shioaji as sj
from datetime import datetime
class ShioajiFeeds(bt.feed.DataBase):
params = (
("person_id", "身份證字號"),
("passwd", "永豐金證券密碼"),
("start", datetime.now().strftime("%Y-%m-%d")),
("end", datetime.now().strftime("%Y-%m-%d")),
("stock", "2330"),
# 這個變數是 DataBase 的
# 因為 Shioaji 取得的資料是每分鐘,所以設成 TimeFrame.Minutes
("timeframe", bt.TimeFrame.Minutes),
)
def start(self):
self.api = sj.Shioaji()
self.api.login(person_id = self.p.person_id, passwd = self.p.passwd)
contract = self.api.Contracts.Stocks[self.p.stock]
self.kbar = self.api.kbars(contract, start = self.p.start, end = self.p.end)
self.idx = 0
self.dataLength = len(self.kbar.ts) # 紀錄我們取得的資料有多少筆
def _load(self):
# 資料會一筆一筆讀進來,如果讀到最後一筆,就跳出 False
if (self.idx == self.dataLength):
return False
dt = datetime.utcfromtimestamp(self.kbar.ts[self.idx] / 10**9)
self.lines.datetime[0] = date2num(dt)
self.lines.open[0] = self.kbar.Open[self.idx]
self.lines.high[0] = self.kbar.High[self.idx]
self.lines.low[0] = self.kbar.Low[self.idx]
self.lines.close[0] = self.kbar.Close[self.idx]
self.lines.volume[0] = self.kbar.Volume[self.idx]
self.idx += 1
return True
def stop(self):
# 結束的時候進行登出
self.api.logout()
self.api = None
# 使用自訂的 datafeed
cerebro = bt.Cerebro()
data = ShioajiFeeds(start='2021-10-01', end='2021-10-08', stock='2303')
cerebro.resampledata(data, timeframe=bt.TimeFrame.Days)
cerebro.addstrategy(bt.Strategy)
cerebro.run()
cerebro.plot()
那如果我們要載入多筆股票的資料,以這個寫法,需要進行登入登出多次,所以可以稍微改一下,把 api 當成一個參數去執行
class ShioajiFeeds(bt.feed.DataBase):
params = (
("person_id", "身份證字號"),
("passwd", "永豐金證券密碼"),
("start", datetime.now().strftime("%Y-%m-%d")),
("end", datetime.now().strftime("%Y-%m-%d")),
("stock", "2330"),
("timeframe", bt.TimeFrame.Minutes),
("api", None), # 新增一個 api 參數
)
def start(self):
if self.p.api is None:
self.api = sj.Shioaji()
self.api.login(person_id = self.p.person_id, passwd = self.p.passwd)
else:
self.api = self.p.api
# ...略
def stop(self):
if self.p.api is None:
self.api.logout()
self.api = None
在執行的時候,我們就可以先初始化 api,然後把 api 當參數傳入,就可以不用一直登入登出了
api = sj.Shioaji()
api.login(person_id = "帳號", passwd = "密碼")
data = ShioajiFeeds(start = '2021-10-01', end = '2021-10-08', stock = '2330', api = api)
# ... 略
# 結束後,記得登出
api.logout()