以下內容皆參考 Backtrader 官網
在評估股票的時候,我們常常會用一些指標來輔助,今天來介紹的是移動平均線,像是 5日線、月線、季線...等。在 backtrader 裡,移動平均線也和 Open, High, Low, Close...等一樣的,我們可以在某個時間點,取出對應的值來進行評估。
程式碼如下:
class TestStrategy(bt.Strategy):
#...略
def __init__(self):
#...略
# period 就是我們要取幾日去做平均
self.sma = bt.indicators.SimpleMovingAverage(self.data, period=5)
如果想要懶一點,self.data 預設就是第一組數據,除非你不是用第一組,不然可以省略
假設我們今天要做一個策略如下:
程式碼如下:
class TestStrategy(bt.Strategy):
#...略
def next(self):
self.log("收盤價: %.2f" % self.dataclose[0])
if self.order:
return
if not self.position:
if self.dataclose[0] > self.sma[0]:
self.log("買入 %.2f" % self.dataclose[0])
self.order = self.buy()
else:
if self.dataclose[0] < self.sma[0]:
self.log("賣出 %.2f" % self.dataclose[0])
self.order = self.sell()
這邊如果你有下去跑,而且輸出結果來看的話,會發現它起始的日期不是 2020-01-02,因為移動平均線是前幾是的收盤價的平圴,所以它會累積到足夠的數據才會開始執行回測的策略。
再來,配合昨天的參數化,把移動平均線參數化
class TestStrategy(bt.Strategy):
params = (
( "maperiod", 5 ),
( "defaultSize", 1000 ),
( "printLog", False ),
)
#...略
def __init__(self):
self.dataclose[0] = self.data.close
self.sizer.setsizing(self.p.defaultSize)
self.sma = bt.indicators.SimpleMovingAverage(period = self.p.maperiod)
self.order = None
有了參數化之後,我們還可以進行參數的最佳化,以移動平均來說,要用 5日、月線或者是季線來當我們的指標呢?這裡就可以指定多組參數,讓 backtrader 去執行回測,我們就可以看到不同值結果。
cerebro.optstrategy(
TestStrategy,
maperiod=range(10, 31)
)
因為我們有多組的參數去跑回測,所以我們關注的結果就是每個參數的結果,要取得這個結果,可以利用策略裡的 stop 事件,當回測跑完之後就會去執行這一個程式
class TestStrategy(bt.Strategy):
#...略
def stop(self):
self.log("MA Period %2d 結束收益 %.2f" % (self.p.maperiod, self.broker.getvalue()), doPrint = True)
完整程式碼如下:
'''
- 低於 均線 買入
- 高於 均線 賣出
- 一買一賣
'''
import backtrader as bt
class TestStrategy(bt.Strategy):
params = (
('maperiod', 15),
('defaultSize', 1000),
('printlog', False),
)
def log(self, txt, dt = None, doPrint = False):
''' Log function for this strategy '''
if self.params.printlog or doPrint:
dt = dt or self.datas[0].datetime.date(0)
print("%s %s" % (dt.isoformat(), txt))
def __init__(self):
self.dataclose = self.data.close
self.sizer.setsizing(self.p.defaultSize)
self.sma = bt.indicators.SimpleMovingAverage(period = self.p.maperiod)
# pendding order
self.order = None
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
self.log("買入 價格: %.2f 成本: %.2f 手續費: %.2f" % (order.executed.price, order.executed.value, order.executed.comm))
elif order.issell():
self.log("賣出 價格: %.2f 成本: %.2f 手續費: %.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("交易取消/餘額不足/拒絕交易")
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log("交易收益: 毛利 %.2f 淨利 %.2f" % (trade.pnl, trade.pnlcomm))
def next(self):
self.log("收盤價: %.2f" % self.dataclose[0])
if self.order:
return
if not self.position:
if self.dataclose[0] > self.sma[0]:
self.log("買入 %.2f" % self.dataclose[0])
self.order = self.buy()
else:
if self.dataclose[0] < self.sma[0]:
self.log("賣出 %.2f" % self.dataclose[0])
self.order = self.sell()
def stop(self):
self.log('MA Period %2d 結束收益 %.2f' % (self.params.maperiod, self.broker.getvalue()), doPrint = True)
cerebro = bt.Cerebro()
# set strategy
cerebro.optstrategy(
TestStrategy,
maperiod = range(10, 31)
)
# set cash
cerebro.broker.setcash(10**6)
# set commission
cerebro.broker.setcommission(commission=0.1425/100)
data = bt.feeds.PandasData(dataname=df, timeframe=bt.TimeFrame.Minutes)
cerebro.resampledata(data, timeframe=bt.TimeFrame.Days)
cerebro.run()