設(shè)計師可以做兼職的網(wǎng)站/馮耀宗seo
本文檔參考backtrader官方文檔,是官方文檔的完整中文翻譯,可作為backtrader中文教程、backtrader中文參考手冊、backtrader中文開發(fā)手冊、backtrader入門資料使用。
快速入門章節(jié)目錄
- 快速入門
- 使用平臺
- 從0到100:一步一步的演示
- 基本設(shè)置
- 設(shè)置現(xiàn)金
- 添加一個數(shù)據(jù)源
- 我們的第一個策略
- 給策略添加執(zhí)行邏輯
- 不僅要買...還要賣
- 券商說:打錢!
- 自定義策略:參數(shù)
- 添加指標(biāo)
- 可視化檢查:繪圖
- 讓我們優(yōu)化
- 結(jié)論
快速入門
使用平臺
讓我們通過一系列的示例(從幾乎空白到完全成熟的策略)來運行,但在此之前,讓我們大致解釋一下使用backtrader時的兩個基本概念。
-
Lines線
數(shù)據(jù)源、指標(biāo)和策略都有線。
一條線是一系列點的連續(xù)性,這些點連接在一起形成這條線。當(dāng)談?wù)撌袌鰰r,數(shù)據(jù)源通常每天有以下一組點:
- Open, High, Low, Close, Volume, OpenInterest (開盤價、最高價、最低價、收盤價、成交量、持倉量)
時間序列中的
開盤價
是一條線。因此,數(shù)據(jù)源通常有6條線。如果我們還考慮
DateTime
(這是單個點的實際參考),我們可以計算出7條線。 -
索引0的用法
在訪問線中的值時,使用索引:0訪問當(dāng)前值
last
輸出值使用*-1訪問。這符合Python可迭代對象的慣例(線可以迭代,因此是可迭代的),其中索引-1*用于訪問可迭代/數(shù)組的last
項。在我們的情況下,訪問的是最后一個輸出值。
因此,索引0緊隨*-1*之后,用于訪問線中的當(dāng)前時刻。
有了這個想法,如果我們想在初始化期間創(chuàng)建一個簡單移動平均線的策略:
self.sma = SimpleMovingAverage(.....)
訪問此移動平均線的當(dāng)前值的最簡單和最簡單的方法是:
av = self.sma[0]
無需知道已處理了多少個bar/分鐘/天/月,因為0
是當(dāng)前時刻的唯一標(biāo)識。
按照Python的傳統(tǒng),使用*-1*訪問last
輸出值:
previous_value = self.sma[-1]
當(dāng)然,可以使用-2、-3等訪問早期的輸出值。
從0到100:一步一步的演示
基本設(shè)置
讓我們開始吧。
from __future__ import (absolute_import, division, print_function,unicode_literals)
# 導(dǎo)入Backtrader
import backtrader as btif __name__ == '__main__':# 創(chuàng)建一個Cerebro引擎實例cerebro = bt.Cerebro()# 打印初始資金print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# 執(zhí)行回測cerebro.run()# 打印最終資金print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
上面示例代碼實現(xiàn)(雖然什么也沒做,但是流程走完了):
- 導(dǎo)入Backtrader
- 實例化Cerebro引擎
- 打印初始資金
- 執(zhí)行回測
- 打印最終資金
代碼的執(zhí)行結(jié)果(小白注意不用拷貝執(zhí)行,這是運行后得到的內(nèi)容):
Starting Portfolio Value: 10000.00
Final Portfolio Value: 10000.00
在這個例子中:
-
導(dǎo)入了backtrader
-
實例化了Cerebro引擎
-
生成的cerebro實例被告知run(循環(huán)數(shù)據(jù))
-
并打印出了結(jié)果
雖然看起來不起眼,但讓我們明確指出一些東西:
- Cerebro引擎在后臺創(chuàng)建了一個broker實例
- 該實例已經(jīng)有一些現(xiàn)金可以開始交易
這種幕后經(jīng)紀(jì)人實例化是平臺中的一個常見特征,以簡化用戶的生活。如果用戶沒有設(shè)置經(jīng)紀(jì)人,則會放置一個默認(rèn)經(jīng)紀(jì)人。
而10K的測試貨幣量是回測常用的一個貨幣價值。
設(shè)置現(xiàn)金
在金融世界中,只有輸家
才從10k開始。讓我們改變現(xiàn)金并再次運行示例。
from __future__ import (absolute_import, division, print_function,unicode_literals)import backtrader as btif __name__ == '__main__':cerebro = bt.Cerebro()cerebro.broker.setcash(100000.0)print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())cerebro.run()print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
執(zhí)行后的輸出為:
Starting Portfolio Value: 1000000.00Final Portfolio Value: 1000000.00
本節(jié)任務(wù)完成,讓我們提升任務(wù)的難度。
添加一個數(shù)據(jù)源
有現(xiàn)金很有趣,但所有這一切的目的是讓策略在我們提供的數(shù)據(jù)源資產(chǎn)上自動操作,而不需要動手。
因此…沒有數(shù)據(jù)源->沒有樂趣。讓我們將其添加到不斷增長的示例中。
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # 用于日期時間對象
import os.path # 用于管理路徑
import sys # 用于查找腳本名稱(在argv [0]中)# 導(dǎo)入backtrader平臺
import backtrader as btif __name__ == '__main__':# 創(chuàng)建cerebro實體cerebro = bt.Cerebro()# 數(shù)據(jù)在樣本的子文件夾中。需要找到腳本所在的位置# 因為它可以從任何地方調(diào)用modpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# 創(chuàng)建數(shù)據(jù)源data = bt.feeds.YahooFinanceCSVData(dataname=datapath,# 不傳遞此日期之前的值fromdate=datetime.datetime(2000, 1, 1),# 不傳遞此日期之后的值todate=datetime.datetime(2000, 12, 31),reverse=False)# 將數(shù)據(jù)源添加到Cerebrocerebro.adddata(data)# 設(shè)置我們所需的現(xiàn)金起始值cerebro.broker.setcash(100000.0)# 打印出起始條件print('起始投資組合價值:%.2f' % cerebro.broker.getvalue())# 運行所有cerebro.run()# 打印出最終結(jié)果print('最終投資組合價值:%.2f' % cerebro.broker.getvalue())
執(zhí)行后的輸出為:
起始投資組合價值:100000.00
最終投資組合價值:100000.00
模板代碼的數(shù)量略有增加,因為我們添加了:
-
找出我們的示例腳本所在的位置,以便能夠定位示例數(shù)據(jù)源文件
-
有datetime對象以過濾我們將要操作的數(shù)據(jù)源中的數(shù)據(jù)
除此之外,Data Feed被創(chuàng)建并添加到cerebro中。
輸出沒有改變,如果它改變了,那將是一個奇跡。
注意:Yahoo Online以日期降序發(fā)送CSV數(shù)據(jù),這不是標(biāo)準(zhǔn)約定。reversed=True參數(shù)考慮到CSV數(shù)據(jù)在文件中已經(jīng)被反轉(zhuǎn)并具有標(biāo)準(zhǔn)預(yù)期的日期升序。
我們的第一個策略
現(xiàn)金在broker中,Data Feed也在那里。我們馬上就可以跑生意了。
讓我們將策略放入方程式中,并打印每天(bar)的Close
價格。
DataSeries(Data Feeds中的基礎(chǔ)類)對象具有訪問眾所周知的OHLC(Open High Low Close)每日值的別名。這應(yīng)該簡化我們的打印邏輯的創(chuàng)建。
# 導(dǎo)入所需模塊
from __future__ import (absolute_import, division, print_function, unicode_literals)
import datetime # 日期時間模塊
import os.path # 路徑模塊
import sys # 系統(tǒng)模塊# 導(dǎo)入backtrader平臺
import backtrader as bt# 創(chuàng)建策略
class TestStrategy(bt.Strategy):def log(self, txt, dt=None):''' 日志函數(shù) '''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# 保留對數(shù)據(jù)序列中`close`線的引用self.dataclose = self.datas[0].closedef next(self):# 記錄數(shù)據(jù)序列的收盤價self.log('收盤價, %.2f' % self.dataclose[0])if __name__ == '__main__':# 創(chuàng)建cerebro實體cerebro = bt.Cerebro()# 添加策略cerebro.addstrategy(TestStrategy)# 數(shù)據(jù)在樣本的子文件夾中。需要找到腳本所在的位置,因為它可以從任何地方調(diào)用modpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# 創(chuàng)建數(shù)據(jù)源data = bt.feeds.YahooFinanceCSVData(dataname=datapath,# 不傳遞此日期之前的值fromdate=datetime.datetime(2000, 1, 1),# 不傳遞此日期之前的值todate=datetime.datetime(2000, 12, 31),reverse=False)# 將數(shù)據(jù)源添加到Cerebrocerebro.adddata(data)# 設(shè)置初始資金cerebro.broker.setcash(100000.0)# 打印初始條件print('初始資產(chǎn)價值: %.2f' % cerebro.broker.getvalue())# 運行策略cerebro.run()# 打印最終結(jié)果print('最終資產(chǎn)價值: %.2f' % cerebro.broker.getvalue())
執(zhí)行后的輸出為::
初始資產(chǎn)價值: 100000.00
2000-01-03, 收盤價, 26.27
2000-01-04, 收盤價, 23.95.........
2000-12-28, 收盤價, 27.63
2000-12-29, 收盤價, 25.85
最終資產(chǎn)價值: 100000.00
有人說股票市場是冒險的生意,但似乎并非如此。
讓我們解釋一些神奇的事:
-
在調(diào)用__init__時,策略已經(jīng)有了一個存在于平臺中的數(shù)據(jù)列表
這是一個標(biāo)準(zhǔn)的Python的list,可以按照它們插入的順序訪問數(shù)據(jù)。
列表中的第一個數(shù)據(jù)
self.datas[0]
是用于交易操作的默認(rèn)數(shù)據(jù),并且為了保持所有策略元素同步(它是系統(tǒng)時鐘) -
self.dataclose = self.datas[0].close
保留對close line的引用。只需要一個間接級別即可訪問關(guān)閉值。 -
策略
next
方法將在系統(tǒng)時鐘(self.datas[0])的每個柱上調(diào)用。這是真實的,直到其他事情進(jìn)入比如indicators,它需要一些柱才能開始產(chǎn)生輸出。稍后再說。
給策略添加執(zhí)行邏輯
讓我們嘗試一些瘋狂的想法,通過查看一些圖表
- 如果價格連續(xù)下跌3個會話… 買買買!!!
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # 導(dǎo)入日期時間庫
import os.path # 導(dǎo)入路徑管理庫
import sys # 導(dǎo)入系統(tǒng)庫# 導(dǎo)入backtrader平臺
import backtrader as bt# 創(chuàng)建策略
class TestStrategy(bt.Strategy):def log(self, txt, dt=None):''' 日志記錄函數(shù)'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# 保留對數(shù)據(jù)序列中`close`線的引用self.dataclose = self.datas[0].closedef next(self):# 記錄數(shù)據(jù)序列的收盤價self.log('Close, %.2f' % self.dataclose[0])if self.dataclose[0] < self.dataclose[-1]:# 當(dāng)前收盤價小于前一個收盤價if self.dataclose[-1] < self.dataclose[-2]:# 前一個收盤價小于前一個收盤價# 買入self.log('BUY CREATE, %.2f' % self.dataclose[0])self.buy()if __name__ == '__main__':# 創(chuàng)建cerebro實體cerebro = bt.Cerebro()# 添加策略cerebro.addstrategy(TestStrategy)# 數(shù)據(jù)在樣本的子文件夾中。需要找到腳本所在的位置# 因為它可以從任何地方調(diào)用modpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# 創(chuàng)建數(shù)據(jù)源data = bt.feeds.YahooFinanceCSVData(dataname=datapath,# 不要傳遞此日期之前的值fromdate=datetime.datetime(2000, 1, 1),# 不要傳遞此日期之前的值todate=datetime.datetime(2000, 12, 31),# 不要傳遞此日期之后的值reverse=False)# 將數(shù)據(jù)源添加到Cerebrocerebro.adddata(data)# 設(shè)置我們的期望現(xiàn)金起始值cerebro.broker.setcash(100000.0)# 打印出起始條件print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# 運行策略cerebro.run()# 打印出最終結(jié)果print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
執(zhí)行后的輸出為:
Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, Close, 21.35
2000-01-06, BUY CREATE, 21.35
...
...
...
2000-12-20, BUY CREATE, 25.35
2000-12-21, Close, 26.24
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-27, BUY CREATE, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
Final Portfolio Value: 99740.45
發(fā)出了幾個買入
訂單,我們的組合價值減少了。顯然缺少了一些重要的事情。
-
訂單已創(chuàng)建,但不知道是否執(zhí)行,何時執(zhí)行以及以什么價格執(zhí)行。
下一個示例將在此基礎(chǔ)上構(gòu)建,通過監(jiān)聽訂單狀態(tài)的通知來解決這個問題。
好奇的讀者可能會問買了多少股票,買了哪些資產(chǎn)以及如何執(zhí)行訂單。在可能的情況下(在這種情況下),平臺填補(bǔ)了這些空白:
- self.datas[0](主數(shù)據(jù),即系統(tǒng)時鐘)是操作買賣的目標(biāo)資產(chǎn),如果沒有指定其他資產(chǎn)的話(即默認(rèn)操作datas[0])
- 買賣股份數(shù)量由position sizer在幕后提供,使用固定股份,即默認(rèn)值
1
。它將在稍后的示例中進(jìn)行修改 - 訂單是
市價
執(zhí)行的。經(jīng)紀(jì)人(在前面的示例中顯示)使用下一個條的開盤價執(zhí)行此操作,因為那是當(dāng)前正在檢查的條下一個tick。 - 訂單迄今為止沒有任何傭金(稍后會詳細(xì)介紹)
不僅要買…還要賣
在了解如何進(jìn)入市場(多頭)之后,需要一個退出概念
,即給策略添加從市場退出的邏輯(賣股)。
- 幸運的是,Strategy對象提供了對默認(rèn)數(shù)據(jù)源的position屬性的訪問
- 方法buy和sell返回已創(chuàng)建(尚未執(zhí)行)的訂單
- 訂單狀態(tài)的更改將通過notify方法通知策略
退出概念
將是一個簡單的概念:
-
在5個bar(第6個bar)之后退出,無論好壞
請注意,1個bar的bar可以表示1分鐘,1小時,1天,1周或其他任何長度的時間單位。
盡管我們知道數(shù)據(jù)源是每日的,但策略不會對此做出任何假設(shè)。
此外,為了簡化:
- 僅在尚未進(jìn)入市場時才允許購買訂單
注意:next方法沒有傳遞
bar索引
,因此似乎不清楚如何理解何時可能已經(jīng)過去了5個bar,但是這已經(jīng)以pythonic的方式建模:在對象上調(diào)用len,它將告訴您其lines的長度。
只需在操作中記錄(保存在變量中)已經(jīng)迭代過的bar的長度,然后查看當(dāng)前長度是否相距5個bar。
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # 導(dǎo)入datetime模塊
import os.path # 導(dǎo)入os.path模塊
import sys # 導(dǎo)入sys模塊# 導(dǎo)入backtrader平臺
import backtrader as bt# 創(chuàng)建策略
class TestStrategy(bt.Strategy):def log(self, txt, dt=None):''' 日志記錄函數(shù)'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# 保留對數(shù)據(jù)序列中`close`線的引用self.dataclose = self.datas[0].close# 跟蹤待處理訂單self.order = Nonedef notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# 買入/賣出訂單已提交/已接受 - 無需操作return# 檢查訂單是否已完成# 注意:如果現(xiàn)金不足,經(jīng)紀(jì)人可能會拒絕訂單if order.status in [order.Completed]:if order.isbuy():self.log('買入已執(zhí)行, %.2f' % order.executed.price)elif order.issell():self.log('賣出已執(zhí)行, %.2f' % order.executed.price)self.bar_executed = len(self)elif order.status in [order.Canceled, order.Margin, order.Rejected]:self.log('訂單已取消/保證金不足/拒絕')# 記錄:沒有待處理訂單self.order = Nonedef next(self):# 僅記錄參考系列的收盤價self.log('Close, %.2f' % self.dataclose[0])# 檢查是否有待處理訂單...如果有,我們不能發(fā)送第二個訂單if self.order:return# 檢查是否在市場中if not self.position:# 還沒有...如果...if self.dataclose[0] < self.dataclose[-1]:# 當(dāng)前收盤價小于前一個收盤價if self.dataclose[-1] < self.dataclose[-2]:# 前一個收盤價小于前一個收盤價# 買入self.log('買入創(chuàng)建, %.2f' % self.dataclose[0])# 記錄已創(chuàng)建的訂單,以避免產(chǎn)生第二個訂單self.order = self.buy()else:# 已經(jīng)在市場中...我們可能會賣出if len(self) >= (self.bar_executed + 5):# 賣出self.log('賣出創(chuàng)建, %.2f' % self.dataclose[0])# 記錄已創(chuàng)建的訂單,以避免第二個訂單self.order = self.sell()if __name__ == '__main__':# 創(chuàng)建cerebro實體cerebro = bt.Cerebro()# 添加策略cerebro.addstrategy(TestStrategy)# 數(shù)據(jù)在樣本的子文件夾中。需要找到腳本所在的位置# 因為它可以從任何地方調(diào)用modpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# 創(chuàng)建數(shù)據(jù)源data = bt.feeds.YahooFinanceCSVData(dataname=datapath,# 不要傳遞此日期之前的值fromdate=datetime.datetime(2000, 1, 1),# 不傳遞此日期之后的值todate=datetime.datetime(2000, 12, 31),reverse=False)# 將數(shù)據(jù)源添加到Cerebrocerebro.adddata(data)# 設(shè)置我們的期望現(xiàn)金起始值cerebro.broker.setcash(100000.0)# 打印出起始條件print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# 運行整個策略cerebro.run()# 打印出最終結(jié)果print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
執(zhí)行后的輸出為(打印日志是源文檔未翻譯,但代碼中打印內(nèi)容已翻譯):
Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, 買入創(chuàng)建, 22.68
2000-01-06, 買入已執(zhí)行, 22.27
...
...
2000-12-20, Close, 25.35
2000-12-20, 買入創(chuàng)建, 25.35
2000-12-21, 買入已執(zhí)行, 24.74
2000-12-21, Close, 26.24
...
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, 賣出創(chuàng)建, 25.85
Final Portfolio Value: 100017.52
系統(tǒng)賺了錢…一定有什么問題
券商說:打錢!
這筆錢叫做傭金
。
讓我們?yōu)槊看尾僮?#xff08;買入和賣出)添加合理的*0.1%*傭金率(是的,經(jīng)紀(jì)人很貪婪……)
只需要一行代碼:
# 0.1% ... divide by 100 to remove the %
cerebro.broker.setcommission(commission=0.001)
熟悉平臺后,我們想在買賣周期之后查看有無傭金的利潤或損失。
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = Nonedef notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif order.status in [order.Completed]:if order.isbuy():self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.buyprice = order.executed.priceself.buycomm = order.executed.commelse: # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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('Order Canceled/Margin/Rejected')self.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] < self.dataclose[-1]:# current close less than previous closeif self.dataclose[-1] < self.dataclose[-2]:# previous close less than the previous close# BUY, BUY, BUY!!! (with default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:# Already in the market ... we might sellif len(self) >= (self.bar_executed + 5):# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(100000.0)# Set the commission - 0.1% ... divide by 100 to remove the %cerebro.broker.setcommission(commission=0.001)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
執(zhí)行后的輸出為:
Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, BUY EXECUTED, Price: 22.27, Cost: 22.27, Comm 0.02
...
...
...
2000-12-21, BUY EXECUTED, Price: 24.74, Cost: 24.74, Comm 0.02
2000-12-21, Close, 26.24
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100016.06
上帝保佑!系統(tǒng)仍然賺了錢。
在繼續(xù)之前,讓我們通過過濾OPERATION PROFIT
行來注意一些事情:
2000-01-14, OPERATION PROFIT, GROSS 1.97, NET 1.92
2000-02-07, OPERATION PROFIT, GROSS 3.48, NET 3.43
2000-02-28, OPERATION PROFIT, GROSS 4.23, NET 4.17
2000-03-13, OPERATION PROFIT, GROSS 3.28, NET 3.21
2000-03-22, OPERATION PROFIT, GROSS -0.39, NET -0.46
2000-04-07, OPERATION PROFIT, GROSS 2.31, NET 2.24
2000-04-20, OPERATION PROFIT, GROSS -1.83, NET -1.90
2000-05-02, OPERATION PROFIT, GROSS 5.15, NET 5.08
2000-05-11, OPERATION PROFIT, GROSS -3.53, NET -3.59
2000-05-30, OPERATION PROFIT, GROSS -1.39, NET -1.45
2000-07-05, OPERATION PROFIT, GROSS -1.53, NET -1.60
2000-07-14, OPERATION PROFIT, GROSS 1.97, NET 1.90
2000-07-28, OPERATION PROFIT, GROSS 0.14, NET 0.07
2000-08-08, OPERATION PROFIT, GROSS 4.11, NET 4.04
2000-08-21, OPERATION PROFIT, GROSS 0.97, NET 0.90
2000-09-15, OPERATION PROFIT, GROSS -4.00, NET -4.08
2000-09-27, OPERATION PROFIT, GROSS 1.22, NET 1.15
2000-10-13, OPERATION PROFIT, GROSS -2.81, NET -2.87
2000-10-26, OPERATION PROFIT, GROSS 2.84, NET 2.78
2000-11-06, OPERATION PROFIT, GROSS -3.39, NET -3.45
2000-11-16, OPERATION PROFIT, GROSS 1.22, NET 1.17
2000-12-01, OPERATION PROFIT, GROSS 2.45, NET 2.41
2000-12-18, OPERATION PROFIT, GROSS -0.06, NET -0.11
將凈
利潤相加,最終數(shù)字為:
15.83(備注原文數(shù)字,實際執(zhí)行代碼結(jié)果和文檔有差異,此處示意)
但系統(tǒng)在最后說了以下內(nèi)容:
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100016.06
顯然,15.83不是16.06。沒有任何錯誤。15.83的凈
利潤已經(jīng)到手了。
不幸的是(或者幸運的是,為了更好地理解平臺),在數(shù)據(jù)源
最后一天有一個未平倉頭寸。即使已經(jīng)發(fā)送了SELL操作……它還沒有被執(zhí)行。
經(jīng)紀(jì)人計算的最終組合價值
考慮了2000-12-29的收盤
價格。實際執(zhí)行價格將在下一個交易日(即2001-1-3)設(shè)置。將數(shù)據(jù)源
擴(kuò)展到考慮這一天后,輸出為:
2000-12-29, SELL CREATE, 25.85
2001-01-02, SELL EXECUTED, Price: 26.30, Cost: 24.74, Comm 0.03
2001-01-02, OPERATION PROFIT, GROSS 1.56, NET 1.51
2001-01-02, Close, 23.46
2001-01-02, BUY CREATE, 23.46
Final Portfolio Value: 100016.48
現(xiàn)在將前面的凈
利潤添加到已完成操作的凈
利潤中:
15.83 + 1.59 = 17.42(備注原文數(shù)字,實際執(zhí)行代碼結(jié)果和文檔有差異,此處示意)
(忽略print
語句中的舍入誤差),這是初始100000貨幣單位以上的額外組合。
自定義策略:參數(shù)
在策略中硬編碼一些值并沒有什么實際意義,而且很難輕松更改它們。參數(shù)很有用。
定義參數(shù)很容易,看起來像這樣:
params = (('myparam', 27), ('exitbars', 5),)
這是一個標(biāo)準(zhǔn)的Python元組,里面有一些元組,以下可能更吸引人:
params = (('myparam', 27),('exitbars', 5),
)
無論哪種格式化策略都允許在將策略添加到Cerebro引擎時進(jìn)行參數(shù)化:
# Add a strategy
cerebro.addstrategy(TestStrategy, myparam=20, exitbars=7)
注意:下面的
setsizing
方法已棄用。這個內(nèi)容是為了讓任何人查看舊的源代碼示例而保留的。源代碼已經(jīng)更新為使用:
# 已經(jīng)棄用
cerebro.addsizer(bt.sizers.FixedSize, stake=10)``
可以閱讀sizers
相關(guān)章節(jié)查看更多。
在策略中使用參數(shù)很容易,因為它們存儲在params
屬性中。如果我們例如想要設(shè)置固定的股份,我們可以在__init__期間將股份參數(shù)傳遞給position sizer:
# Set the sizer stake from the params
self.sizer.setsizing(self.params.stake)
我們也可以使用stake參數(shù)和self.params.stake作為值來調(diào)用buy和sell。
退出邏輯被修改:
# Already in the market ... we might sell
if len(self) >= (self.bar_executed + self.params.exitbars):
考慮到所有這些,示例的演變?nèi)缦?#xff1a;
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('exitbars', 5),)def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = Nonedef notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif order.status in [order.Completed]:if order.isbuy():self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.buyprice = order.executed.priceself.buycomm = order.executed.commelse: # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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('Order Canceled/Margin/Rejected')self.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] < self.dataclose[-1]:# current close less than previous closeif self.dataclose[-1] < self.dataclose[-2]:# previous close less than the previous close# BUY, BUY, BUY!!! (with default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:# Already in the market ... we might sellif len(self) >= (self.bar_executed + self.params.exitbars):# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(100000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commission - 0.1% ... divide by 100 to remove the %cerebro.broker.setcommission(commission=0.001)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
執(zhí)行后的輸出為:
Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, BUY EXECUTED, Price: 22.27, Cost: 222.70, Comm 0.22
...
...
...
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100160.58
為了看到差異,打印輸出也已擴(kuò)展以顯示執(zhí)行大小。
將股份乘以10后,顯然發(fā)生了以下情況:利潤和損失乘以10。而不是16.58,剩余現(xiàn)金現(xiàn)在是165.80。
添加指標(biāo)
指標(biāo)可以簡單理解為:用于衡量市場趨勢和價格變化的工具。指標(biāo)可以是基于價格、成交量或其他市場數(shù)據(jù)的計算結(jié)果。
聽說過指標(biāo)后,下一步任何人都會添加一個指標(biāo)到策略中。毫無疑問,它們一定比簡單的*3個較低的收盤價*
策略好得多。
受PyAlgoTrade中的一個示例啟發(fā),使用簡單移動平均線的策略。
- 如果收盤價大于平均值,則
以市價
買入 - 如果在市場上,如果收盤價小于平均值,則賣出
- 市場上只允許有1個活動操作
大部分現(xiàn)有的代碼可以保持不變。讓我們在__init__中添加平均值,并保留對它的引用:
self.sma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)
當(dāng)然,進(jìn)入和退出市場的邏輯將依賴于平均值。在代碼中查找邏輯。
注意:: 起始現(xiàn)金將為1000貨幣單位,以與PyAlgoTrade示例保持一致,并且不會應(yīng)用傭金
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('maperiod', 15),)def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = None# Add a MovingAverageSimple indicatorself.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif order.status in [order.Completed]:if order.isbuy():self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.buyprice = order.executed.priceself.buycomm = order.executed.commelse: # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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('Order Canceled/Margin/Rejected')self.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] > self.sma[0]:# BUY, BUY, BUY!!! (with all possible default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:if self.dataclose[0] < self.sma[0]:# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(1000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commissioncerebro.broker.setcommission(commission=0.0)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
現(xiàn)在,在跳轉(zhuǎn)到下一節(jié)之前,請仔細(xì)查看日志中顯示的第一個日期:
-
它不再是2K年的第一個交易日2000-01-03。
它是2000-01-24 …
缺失的天數(shù)并不不是真的缺失。而是平臺已適應(yīng)新的情況:
-
已將指標(biāo)(SimpleMovingAverage)添加到策略中。
-
此指標(biāo)需要X個條形圖才能產(chǎn)生輸出:例如:15
-
2000-01-24是第15個條形圖出現(xiàn)的日期
backtrader平臺假定策略已經(jīng)有了指標(biāo),有一個很好的理由,在決策過程中使用它。如果指標(biāo)尚未準(zhǔn)備好并產(chǎn)生值,則嘗試做出決策是沒有意義的。
-
當(dāng)所有指標(biāo)已經(jīng)達(dá)到產(chǎn)生值所需的最小周期時,next將首先被調(diào)用
-
在示例中只有一個指標(biāo),但是策略可以有任意數(shù)量的指標(biāo)。
執(zhí)行后的輸出為:
Starting Portfolio Value: 1000.00
2000-01-24, Close, 24.10
2000-01-25, Close, 25.10
2000-01-25, BUY CREATE, 25.10
2000-01-26, BUY EXECUTED, Price: 25.24, Cost: 252.40, Comm 0.00
...
...
...
2000-12-21, OPERATION PROFIT, GROSS -19.40, NET -19.40
2000-12-21, Close, 26.24
2000-12-21, BUY CREATE, 26.24
2000-12-22, BUY EXECUTED, Price: 27.02, Cost: 270.20, Comm 0.00
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 975.60
一個獲勝的系統(tǒng)變成了一個失敗的系統(tǒng)…而且沒有傭金。很可能僅僅添加一個指標(biāo),說明指標(biāo)并不是萬能藥。
注意:在PyAlgoTrade中使用相同的邏輯和數(shù)據(jù)會產(chǎn)生略有不同的結(jié)果(略微偏離)。查看整個打印輸出會發(fā)現(xiàn)一些操作不完全相同。罪魁禍?zhǔn)自俅问峭ǔ5南右扇?#xff1a;四舍五入。
PyAlgoTrade在將分裂的調(diào)整收盤價
應(yīng)用于數(shù)據(jù)源值時不會將其舍入到小數(shù)點后2位。
由backtrader提供的Yahoo數(shù)據(jù)源將在應(yīng)用調(diào)整后將值向下舍入到2位小數(shù)。在打印值時,一切似乎都是相同的,但很明顯,有時第5位小數(shù)起作用。
向下舍入到2位小數(shù)似乎更為現(xiàn)實,因為Marke Exchange僅允許每個資產(chǎn)有一定數(shù)量的小數(shù)位數(shù)(通常為股票的2個小數(shù)位數(shù))
注意:Yahoo數(shù)據(jù)源(從版本
1.8.11.99
開始)允許指定是否進(jìn)行舍入以及舍入到多少位小數(shù))
可視化檢查:繪圖
打印或記錄系統(tǒng)在每個bar的實際執(zhí)行情況是可以的,但人類是視覺動物,因此提供一個圖表視圖輸出機(jī)制無疑是正確的。
注意:要繪制圖形,需要安裝matplotlib
再次,默認(rèn)情況下繪圖可幫助平臺用戶。繪圖是一個僅有的1行操作:
cerebro.plot()
在調(diào)用cerebro.run()
后,位置肯定是在那里。
為了顯示自動繪圖功能和一些簡單的自定義,將執(zhí)行以下操作:
- 添加第二個移動平均線(指數(shù))。默認(rèn)情況下將其與數(shù)據(jù)一起繪制(就像第一個一樣)。
- 添加第三個移動平均線(加權(quán))。自定義繪制在自己的圖中(即使不明智)
- 添加隨機(jī)(慢)。默認(rèn)情況下不更改。
- 添加MACD。默認(rèn)情況下不更改。
- 添加RSI。默認(rèn)情況下不更改。
- 對RSI應(yīng)用移動平均線(簡單)。默認(rèn)情況下不更改(將與RSI一起繪制)
- 添加AverageTrueRange。更改默認(rèn)值以避免繪制。
Strategy的init方法中添加的全部內(nèi)容:
# Indicators for the plotting show
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(self.datas[0], period=25).subplot = True
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0]).plot = False
注意:即使indicators沒有顯式添加到策略的成員變量中(例如self.sma = MovingAverageSimple…),它們也將自動注冊到策略中,并影響next的最小周期,并成為繪圖的一部分。
在示例中,只有RSI被添加到臨時變量rsi中,其唯一目的是在其上創(chuàng)建一個MovingAverageSmoothed。
現(xiàn)在的例子:
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('maperiod', 15),)def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = None# Add a MovingAverageSimple indicatorself.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)# Indicators for the plotting showbt.indicators.ExponentialMovingAverage(self.datas[0], period=25)bt.indicators.WeightedMovingAverage(self.datas[0], period=25,subplot=True)bt.indicators.StochasticSlow(self.datas[0])bt.indicators.MACDHisto(self.datas[0])rsi = bt.indicators.RSI(self.datas[0])bt.indicators.SmoothedMovingAverage(rsi, period=10)bt.indicators.ATR(self.datas[0], plot=False)def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif order.status in [order.Completed]:if order.isbuy():self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.buyprice = order.executed.priceself.buycomm = order.executed.commelse: # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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('Order Canceled/Margin/Rejected')# Write down: no pending orderself.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] > self.sma[0]:# BUY, BUY, BUY!!! (with all possible default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:if self.dataclose[0] < self.sma[0]:# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(1000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commissioncerebro.broker.setcommission(commission=0.0)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())# Plot the resultcerebro.plot()
執(zhí)行后的輸出為:
Starting Portfolio Value: 1000.00
2000-02-18, Close, 26.05
2000-02-22, Close, 26.38
2000-02-22, BUY CREATE, 26.38
2000-02-23, BUY EXECUTED, Price: 26.77, Cost: 267.70, Comm 0.00
2000-02-23, Close, 28.05
...
...
...
2000-12-22, BUY EXECUTED, Price: 27.02, Cost: 270.20, Comm 0.00
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 982.30
最終結(jié)果已更改,即使邏輯沒有。這是真的,但邏輯沒有應(yīng)用于相同數(shù)量的bar。
注意:如前所述,平臺將在所有指標(biāo)準(zhǔn)備好生成值時首先調(diào)用next。在這個繪圖示例中(在圖表中非常清晰),MACD是最后一個完全準(zhǔn)備好的指標(biāo)(所有3條線都產(chǎn)生輸出)。第一個BUY訂單不再在2000年1月期間計劃,而是接近2000年2月底。
圖表:
讓我們優(yōu)化
許多交易書籍都說每個市場和每個交易的股票(或商品或…)都有不同的節(jié)奏。沒有一種適合所有人的東西。
在繪圖示例之前,當(dāng)策略開始使用指標(biāo)時,期間的默認(rèn)值為15個bar。這是一個策略參數(shù),可以在優(yōu)化中使用該參數(shù)的值并查看哪個更適合市場。
注意:有很多關(guān)于優(yōu)化和相關(guān)利弊的文獻(xiàn)。但是建議總是指向同一個方向:不要過度優(yōu)化。如果交易想法不合理,則優(yōu)化可能會產(chǎn)生僅對回測數(shù)據(jù)集有效的正面結(jié)果。
示例已修改為優(yōu)化簡單移動平均線的期間。為了清晰起見,已刪除與買入/賣出訂單相關(guān)的任何輸出
現(xiàn)在的例子:
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('maperiod', 15),('printlog', False),)def log(self, txt, dt=None, doprint=False):''' Logging function fot 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):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = None# Add a MovingAverageSimple indicatorself.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif order.status in [order.Completed]:if order.isbuy():self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.buyprice = order.executed.priceself.buycomm = order.executed.commelse: # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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('Order Canceled/Margin/Rejected')# Write down: no pending orderself.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] > self.sma[0]:# BUY, BUY, BUY!!! (with all possible default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:if self.dataclose[0] < self.sma[0]:# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()def stop(self):self.log('(MA Period %2d) Ending Value %.2f' %(self.params.maperiod, self.broker.getvalue()), doprint=True)if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategystrats = cerebro.optstrategy(TestStrategy,maperiod=range(10, 31))# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(1000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commissioncerebro.broker.setcommission(commission=0.0)# Run over everythingcerebro.run(maxcpus=1)
不是調(diào)用addstrategy將策略類添加到Cerebro中,而是調(diào)用optstrategy。而不是傳遞一個值,而是傳遞一系列值。
添加了Strategy
鉤子之一,stop方法,當(dāng)數(shù)據(jù)用盡并且回測結(jié)束時將調(diào)用該方法。它用于在經(jīng)紀(jì)人的投資組合中打印最終凈值(之前在Cerebro中完成)
系統(tǒng)將為范圍內(nèi)的每個值執(zhí)行策略。將輸出以下內(nèi)容:
2000-12-29, (MA Period 10) Ending Value 877.50
2000-12-29, (MA Period 11) Ending Value 878.70
2000-12-29, (MA Period 12) Ending Value 839.80
2000-12-29, (MA Period 13) Ending Value 899.90
2000-12-29, (MA Period 14) Ending Value 902.50
2000-12-29, (MA Period 15) Ending Value 975.60
2000-12-29, (MA Period 16) Ending Value 961.90
2000-12-29, (MA Period 17) Ending Value 952.60
2000-12-29, (MA Period 18) Ending Value 1011.00
2000-12-29, (MA Period 19) Ending Value 1039.40
2000-12-29, (MA Period 20) Ending Value 1073.20
2000-12-29, (MA Period 21) Ending Value 1055.10
2000-12-29, (MA Period 22) Ending Value 1057.60
2000-12-29, (MA Period 23) Ending Value 1021.50
2000-12-29, (MA Period 24) Ending Value 1018.80
2000-12-29, (MA Period 25) Ending Value 1012.40
2000-12-29, (MA Period 26) Ending Value 998.30
2000-12-29, (MA Period 27) Ending Value 983.10
2000-12-29, (MA Period 28) Ending Value 976.90
2000-12-29, (MA Period 29) Ending Value 984.20
2000-12-29, (MA Period 30) Ending Value 980.80
結(jié)果:
- 對于小于18的期間,策略(無傭金)會虧錢。
- 對于18到26之間的期間(包括兩者),策略會賺錢。
- 26以上再次失去了錢。
而該策略和給定數(shù)據(jù)集的獲勝期為:
- 20個bar,贏得1000 $的78.00個單位(7.8%)
注意::繪圖示例中的額外指標(biāo)已被刪除,并且操作的開始僅受到正在優(yōu)化的簡單移動平均線的影響。因此,期間15的結(jié)果略有不同。
結(jié)論
示例逐步的展示了如何從一個簡單的腳本到一個完整的交易系統(tǒng),甚至可以繪制結(jié)果并進(jìn)行優(yōu)化。
可以做很多事情來提高獲勝的機(jī)會:
- 自定義指標(biāo)
創(chuàng)建指標(biāo)很容易(甚至繪制它們也很容易)
- 倉位管理
對于許多人來說,資金管理是成功的關(guān)鍵
-
訂單類型(限價,止損,止損限價)
-
其他一些
為了確??梢猿浞掷蒙鲜鏊许椖?#xff0c;文檔提供了對它們(和其他主題)的深入了解。
查看目錄并繼續(xù)閱讀…并開發(fā)。
祝你好運!