:Python量化回測從入門到多股策略優(yōu)化)
簡介面向量化交易初學(xué)者的Backtrader中文入門教程PDF由CSDN博主整理內(nèi)容整合了官方文檔的核心章節(jié)與源碼解讀適合熟悉Python但剛接觸回測框架的開發(fā)者系統(tǒng)學(xué)習(xí)。教程從Backtrader的安裝配置講起依次覆蓋Cerebro引擎、數(shù)據(jù)源、策略編寫、指標(biāo)庫、訂單與傭金、分析器及實盤交易等核心概念并以示例代碼演示快速回測流程。資源共1個文件、PDF格式壓縮包大小僅1MB便攜易讀目前已有1344人學(xué)習(xí)或下載。除基礎(chǔ)入門外還包含策略與Cerebro源碼參考、TA-Lib集成、tushare與聚寬數(shù)據(jù)源接入、性能優(yōu)化和內(nèi)存管理進(jìn)階技巧可幫助讀者從調(diào)用工具走向理解底層原理快速搭建自己的量化回測系統(tǒng)。1. 寫在開始Backtrader到底是什么能幫你干什么第一次接觸量化交易的人十有八九會被兩個門檻卡住。第一個門檻是策略思路——你到底想怎么賺錢趨勢跟蹤、均值回歸還是套利第二個門檻是技術(shù)實現(xiàn)——你腦子里想的那套規(guī)則怎么才能在歷史數(shù)據(jù)上快速驗證一遍。實話說策略思路這東西急不來但技術(shù)實現(xiàn)完全有捷徑可走而這個捷徑通常就叫Backtrader。Backtrader是一個開源的Python量化回測框架純本地運(yùn)行數(shù)據(jù)自己下策略自己寫結(jié)果自己看。它做的事情說白了就一件把你對“什么時候買入、什么時候賣出”的一套規(guī)則扔進(jìn)歷史行情里跑一遍告訴你這套規(guī)則過去幾年能賺多少錢、最大回撤是多少、勝率有多高。這種驗證方式在行業(yè)內(nèi)叫回測而Backtrader幾乎是個人量化愛好者上手回測的默認(rèn)選擇。這個框架最大的特點一是不需要你從零去寫撮合、滑點、手續(xù)費(fèi)這些交易底層邏輯框架全幫你處理好了二是策略代碼用起來很順手定義好買賣信號就行三是生態(tài)成熟網(wǎng)上關(guān)于它的教程、案例、踩坑記錄非常多遇到問題基本都能搜到答案。我當(dāng)年從研究pandas手動回測轉(zhuǎn)到Backtrader最大的感受就是回測這件事從“造輪子”變成了“用輪子”省下來的時間全都可以花在打磨策略本身上。這篇教程適合三類人看剛接觸量化、想找一個靠譜回測工具的新手已經(jīng)在用其他框架但被各種配置搞到頭疼的開發(fā)者以及有交易想法但不知道怎么快速驗證的投資者。我會從安裝開始講到核心概念再給你一個能跑通的完整策略最后說說多股回測怎么玩。內(nèi)容不深但足夠讓你今天就把第一個回測跑起來。2. 環(huán)境準(zhǔn)備與最小化框架搭建2.1 安裝比你想象中還要簡單Backtrader的安裝過程幾乎沒有什么可說的pip一行命令搞定pip install backtrader如果你的環(huán)境里有多個Python版本記得用pip3或者先激活對應(yīng)的虛擬環(huán)境。裝完之后可以去Python交互環(huán)境里驗證一下import backtrader print(backtrader.__version__)能正常打印版本號就說明安裝成功了。這里提醒一點Backtrader在Python 3.7以上的版本表現(xiàn)都很穩(wěn)定如果你還在用Python 2或者很老的3.x版本建議升級一下否則可能會有兼容性報錯。之前有個朋友卡在導(dǎo)入階段后來發(fā)現(xiàn)是Python 3.5的歷史遺留環(huán)境換了Python 3.9之后一切正常。2.2 最小回測代碼跑通再說理解很多教程喜歡先畫架構(gòu)圖再講概念最后才給代碼。我的習(xí)慣反著來——先給你一段能跑通的代碼讓程序跑起來你親眼看到收益曲線了再回頭理解每個組件是什么。這樣學(xué)起來沒那么抽象。以最經(jīng)典的雙均線策略為例當(dāng)短期均線從下往上穿過長期均線時買入當(dāng)短期均線從上往下穿過長期均線時賣出。用Backtrader實現(xiàn)大概是下面這個結(jié)構(gòu)import backtrader as bt # 1. 定義策略 class SmaCross(bt.Strategy): params ((pfast, 10), (pslow, 30)) def __init__(self): sma_fast bt.indicators.SMA(periodself.p.pfast) sma_slow bt.indicators.SMA(periodself.p.pslow) self.crossover bt.indicators.CrossOver(sma_fast, sma_slow) def next(self): if not self.position: if self.crossover 0: self.buy() elif self.crossover 0: self.close() # 2. 獲取數(shù)據(jù) data bt.feeds.YahooFinanceData(datanameAAPL, fromdatedatetime(2020, 1, 1), todatedatetime(2023, 1, 1)) # 3. 組裝引擎并運(yùn)行 cerebro bt.Cerebro() cerebro.adddata(data) cerebro.addstrategy(SmaCross) cerebro.broker.setcash(100000.0) cerebro.run() cerebro.plot()這段代碼雖然短但已經(jīng)覆蓋了Backtrader的核心五要素策略、數(shù)據(jù)、引擎Cerebro、經(jīng)紀(jì)人Broker、圖表。跑起來之后你會看到一張帶買賣點的K線圖圖上標(biāo)注了每一筆交易的位置那時候你對“回測”這個概念就有了最直觀的理解。3. 核心概念拆解用生活類比一次講清楚3.1 Cerebro整個回測的中央調(diào)度中心Cerebro翻譯過來是“大腦”在Backtrader里它就是整個框架的中樞系統(tǒng)。所有組件都要加到Cerebro里才能運(yùn)行包括數(shù)據(jù)、策略、分析器、觀察器甚至傭金模型和滑點設(shè)置。你可以把它理解成一個導(dǎo)演——數(shù)據(jù)是演員策略是劇本經(jīng)紀(jì)人負(fù)責(zé)管錢Cerebro負(fù)責(zé)導(dǎo)演完整場戲。實際編碼中你至少要掌握Cerebro的幾個核心方法adddata()添加數(shù)據(jù)addstrategy()添加策略注意這里是傳策略類而不是實例broker.setcash()設(shè)置初始資金run()開始回測plot()畫圖。還有后面會講到的addanalyzer()和addobserver()一個用來統(tǒng)計績效指標(biāo)一個用來在圖上畫輔助信息。3.2 Broker那個幫你處理交易細(xì)節(jié)的經(jīng)紀(jì)人很多初學(xué)回測的人會忽略Broker的重要性但恰恰是它幫你處理了成交價、手續(xù)費(fèi)、滑點這些細(xì)節(jié)。Backtrader里的Broker默認(rèn)支持實時成交和成交量檢查這意味著當(dāng)你發(fā)出買入指令時Broker會檢查當(dāng)前K線是否有足夠的成交量來成交而不是無腦按收盤價給你成交。對于新手我建議一開始就設(shè)置好傭金哪怕設(shè)個萬三、千一都行這樣回測結(jié)果里已經(jīng)包含交易成本了。設(shè)置方法很簡單cerebro.broker.setcommission(commission0.001)這里0.001代表千分之一的手續(xù)費(fèi)比例。別小看這一步我見過太多人跑回測沒設(shè)手續(xù)費(fèi)結(jié)果每次都覺得自己找到了印鈔機(jī)實盤一跑就露餡。傭金對策略收益的侵蝕遠(yuǎn)比你想象的大尤其是高頻交易場景傭金可能就是決定盈虧的關(guān)鍵因素。3.3 Strategy與Indicator策略與指標(biāo)的分層邏輯Strategy是Backtrader里最核心的類你要寫的就是繼承它然后重寫幾個關(guān)鍵方法。最常用的是__init__和next兩個方法。__init__里面負(fù)責(zé)創(chuàng)建指標(biāo)、定義線路linenext里面寫在每根K線上要執(zhí)行的邏輯。這里要特別注意Backtrader的一個設(shè)計哲學(xué)__init__里創(chuàng)建的指標(biāo)Backtrader會做自動預(yù)計算能夠在每根K線到來之前就準(zhǔn)備好所有指標(biāo)值。所以你在__init__里寫指標(biāo)計算在next里用self.crossover[0]來取值性能會好很多。Indicator是Backtrader內(nèi)置的豐富指標(biāo)庫SMA、EMA、MACD、RSI、布林帶、ATR全都有直接一行代碼就能創(chuàng)建。你還可以自己寫指標(biāo)繼承bt.Indicator就行。這個我們先不展開后面實操部分會用到。3.4 Data Feed數(shù)據(jù)從哪來怎么接入最省事Backtrader支持的數(shù)據(jù)源很多內(nèi)置的就有Yahoo財經(jīng)、CSV文件、Pandas DataFrame、SQLite數(shù)據(jù)庫等。我最推薦大家從CSV或者Pandas接入原因很簡單——可控性強(qiáng)。無論是從Tushare、AkShare這些免費(fèi)數(shù)據(jù)源拿數(shù)據(jù)還是自己用爬蟲下載最終你都能整理成DataFrame然后喂給Backtrader不用被某個固定數(shù)據(jù)源綁定。使用Pandas數(shù)據(jù)源的示例import pandas as pd from datetime import datetime df pd.read_csv(AAPL.csv, parse_datesTrue, index_coldate) data bt.feeds.PandasData(datanamedf)這里需要注意的是Backtrader默認(rèn)會識別open、high、low、close、volume、openinterest這幾列。如果你的DataFrame列名不是這套標(biāo)準(zhǔn)命名就得用PandasData的參數(shù)做映射data bt.feeds.PandasData( datanamedf, datetimedate, open開盤價, high最高價, low最低價, close收盤價, volume成交量 )把數(shù)據(jù)準(zhǔn)備好之后別忘了檢查時間范圍和數(shù)據(jù)完整性。很多回測結(jié)果失真不是因為策略寫錯了而是數(shù)據(jù)里有缺口或者臟數(shù)據(jù)這個問題在后面排查部分我會詳細(xì)說。4. 實操從寫一個自己的策略到看懂回測報告4.1 策略代碼的完整骨架我把自己常用的策略模板貼出來以后你寫任何策略都可以在這個骨架上修改。import backtrader as bt import datetime class MyStrategy(bt.Strategy): def __init__(self): self.sma_fast bt.indicators.SMA(period10) self.sma_slow bt.indicators.SMA(period30) self.cross bt.indicators.CrossOver(self.sma_fast, self.sma_slow) def next(self): # 當(dāng)前持倉判定 if self.position.size 0: if self.cross[0] 0: self.buy(size100) else: if self.cross[0] 0: self.close()在這個模板里self.position.size表示當(dāng)前持倉數(shù)量為0說明空倉。self.buy(size100)指定了買入100股self.close()表示清倉。你看買賣就是這么簡單。但光有買賣還不夠你總想知道這策略跑完到底賺了多少、回撤多大所以需要在Cerebro里加分析器cerebro bt.Cerebro() cerebro.addstrategy(MyStrategy) cerebro.addanalyzer(bt.analyzers.SharpeRatio, _namesharpe) cerebro.addanalyzer(bt.analyzers.DrawDown, _namedrawdown) cerebro.addanalyzer(bt.analyzers.Returns, _namereturns) results cerebro.run() strategy results[0] print(夏普比率:, strategy.analyzers.sharpe.get_analysis()) print(回撤:, strategy.analyzers.drawdown.get_analysis()) print(收益:, strategy.analyzers.returns.get_analysis())這里我加了三類分析器夏普比率反映風(fēng)險調(diào)整后的收益回撤反映策略最壞情況下的虧損幅度收益反映最終的賺錢能力。這三個指標(biāo)基本夠你評估一個策略的初步質(zhì)量了。4.2 參數(shù)優(yōu)化別只試一組參數(shù)策略寫出來之后參數(shù)的選擇直接決定了回測結(jié)果。均線用5日和20日還是10日和60日這個不能拍腦袋最常用的方法是做參數(shù)網(wǎng)格搜索。Backtrader對此有非常好的支持直接用addstrategy傳參數(shù)列表就行cerebro.optstrategy(MyStrategy, sma_fastrange(5, 21, 5), sma_slowrange(20, 81, 10))注意optstrategy和addstrategy的區(qū)別addstrategy跑一組參數(shù)optstrategy會遍歷所有參數(shù)組合并分別回測。參數(shù)優(yōu)化完成后可以對比不同參數(shù)組合的收益、回撤和夏普比率選出相對穩(wěn)健的一組。這里要強(qiáng)調(diào)一個新手最容易犯的錯誤參數(shù)過度優(yōu)化。如果你把參數(shù)調(diào)到過去數(shù)據(jù)表現(xiàn)最好的位置不代表未來也能賺錢因為歷史高點和低點已經(jīng)被這組參數(shù)“記憶”住了。業(yè)內(nèi)管這叫過擬合。我的建議是參數(shù)優(yōu)化結(jié)果一定要分成樣本內(nèi)和樣本外做驗證或者做滾動回測保證策略在不同時間段都穩(wěn)定而不是只在一段特定行情里表現(xiàn)好。4.3 回測報告怎么看什么樣的策略算好跑完回測別急著激動先把幾個關(guān)鍵指標(biāo)讀懂指標(biāo)含義經(jīng)驗參考總收益率回測期間累計收益越高越好但要結(jié)合回撤看年化收益率折算成年度的收益水平超過年化20%就要警惕過擬合最大回撤從最高點到最低點的最大跌幅20%以內(nèi)相對可控夏普比率單位風(fēng)險獲得的超額收益大于1算及格大于2算優(yōu)秀交易次數(shù)整個回測周期內(nèi)買賣總數(shù)太少樣本不足太多手續(xù)費(fèi)侵蝕勝率盈利交易占總交易的比例趨勢策略勝率普遍低于50%屬正常有些新手看到總收益率300%就興奮得睡不著其實完全沒必要。正確的打開方式應(yīng)該是首先看最大回撤能不能接受再看交易次數(shù)會不會太少最后把收益率和基準(zhǔn)指數(shù)做對比。如果一只策略過去三年收益只有20%但你拿的是大牛市那這個策略實際是跑輸市場的不值得實盤。5. 進(jìn)階實戰(zhàn)backtrader多股回測該怎么做5.1 為什么要做多股回測單只股票的回測有個天然缺陷你的策略可能只在這只股票上表現(xiàn)好換成另一只就完全失效。多股回測的核心價值在于驗證策略的普適性——把同樣的策略放在多只股票上跑一遍如果大多數(shù)都能盈利說明策略捕捉到的是市場規(guī)律而不是某只股票的運(yùn)氣。另外多股回測也是構(gòu)建投資組合的基礎(chǔ)。單只股票的波動率通常很高通過多品種分散配置可以在不降低預(yù)期收益的情況下顯著降低組合的波動。我最早做多股回測的初衷就是把資金分散在十只不同的股票上每只股票根據(jù)同樣的策略信號獨(dú)立交易整體組合的收益曲線比單只股票平滑太多了。5.2 多股回測的兩種常見實現(xiàn)在Backtrader里多股回測的實現(xiàn)方式大致分兩種。第一種是把多只股票的數(shù)據(jù)全部塞進(jìn)同一個Cerebro里每只股票作為一個數(shù)據(jù)源。策略里可以用self.datas[i]來引用不同的數(shù)據(jù)源。這種方式適合所有股票用同一套規(guī)則策略在每只股票上獨(dú)立判斷信號并獨(dú)立建倉。第二種是為每只股票單獨(dú)創(chuàng)建一個Cerebro實例跑完再匯總結(jié)果。這種方案代碼寫起來更直觀邏輯獨(dú)立但缺點是如果股票數(shù)量比較多循環(huán)跑起來相對慢一些。先看第一種方案的邏輯示例cerebro bt.Cerebro() for ticker in [AAPL, MSFT, GOOGL, AMZN, TSLA]: df load_data_from_somewhere(ticker) data bt.feeds.PandasData(datanamedf) cerebro.adddata(data, nameticker) cerebro.addstrategy(MultiStockStrategy)在這個策略里如果想要分別處理每只股票的信號可以在next()里遍歷self.datasclass MultiStockStrategy(bt.Strategy): def __init__(self): self.crosses {} for data in self.datas: sma_fast bt.indicators.SMA(data, period10) sma_slow bt.indicators.SMA(data, period30) self.crosses[data._name] bt.indicators.CrossOver(sma_fast, sma_slow) def next(self): for data in self.datas: name data._name pos self.getposition(data) if not pos.size: if self.crosses[name][0] 0: self.buy(datadata) else: if self.crosses[name][0] 0: self.close(datadata)注意bt.indicators.SMA(data, period10)這里把data作為第一個參數(shù)傳進(jìn)去了表示指標(biāo)基于哪條數(shù)據(jù)線計算。self.getposition(data)返回該數(shù)據(jù)源上的當(dāng)前倉位。self.buy(datadata)明確指定買入哪個數(shù)據(jù)源這樣每只股票就能獨(dú)立運(yùn)行同一套策略。5.3 組合維度分析關(guān)注整體凈值和曲線多股回測和單股回測在后處理上最大的區(qū)別是你需要關(guān)注整個組合的表現(xiàn)而不是單個標(biāo)的的表現(xiàn)。Backtrader默認(rèn)會把所有倉位合并計算賬戶凈值所以results[0].analyzers.returns.get_analysis()拿到的就是整個組合的收益情況。如果把每只股票分開統(tǒng)計可以給每個數(shù)據(jù)源單獨(dú)添加觀察器或者在策略里自定義執(zhí)行邏輯用列表記錄每筆交易的盈虧來源。一個簡單做法是在策略里維護(hù)一個字典按數(shù)據(jù)源名字記錄每筆交易的毛利和凈利最終回測輸出時再做匯總。class MultiStockStrategy(bt.Strategy): def __init__(self): self.trades {} def notify_trade(self, trade): if trade.isclosed: name trade.data._name if name not in self.trades: self.trades[name] {count: 0, profit: 0.0} self.trades[name][count] 1 self.trades[name][profit] trade.pnlcomm def stop(self): # 回測結(jié)束時輸出每只股票的交易統(tǒng)計 for name, stat in self.trades.items(): print(f{name}: 交易次數(shù) {stat[count]}, 凈利 {stat[profit]:.2f})notify_trade是Backtrader里的一個回調(diào)方法當(dāng)一筆交易結(jié)束時自動觸發(fā)trade.pnlcomm表示扣除手續(xù)費(fèi)后的凈盈虧。stop方法在回測結(jié)束時自動調(diào)用正好適合做統(tǒng)計匯總輸出。5.4 多股回測必須注意的三個坑第一數(shù)據(jù)對齊。不同股票的交易日期不完全一致有的股票中途停牌有的股票上市時間晚。Backtrader默認(rèn)會以所有數(shù)據(jù)源的共同時間范圍作為回測區(qū)間如果其中有一只股票數(shù)據(jù)從2022年才開始那2020到2021年回測就根本不會跑。你得先檢查每只股票的數(shù)據(jù)覆蓋范圍要么統(tǒng)一長度要么明確自己接受較短的共同區(qū)間。第二行情的未來函數(shù)。如果你一不小心把數(shù)據(jù)算錯——比如用了未來某天的價格計算指標(biāo)——回測收益會漂亮得離譜但這個收益完全是假的。Backtrader的指標(biāo)機(jī)制本身就規(guī)避了大部分未來函數(shù)問題但你在自定義邏輯時要小心比如在next里使用了self.data.close[0]是當(dāng)前這根K線的收盤價用self.data.close[-1]才是上一根K線的價格千萬別混淆。第三過多的股票數(shù)量會拖慢回測速度。數(shù)據(jù)源越多每根K線上需要執(zhí)行的邏輯就越多回測時間會線性增加。如果股票數(shù)量上百建議考慮用向量化回測的方式或者減少回測時間范圍先做初步驗證確認(rèn)策略有效后再擴(kuò)展到全量數(shù)據(jù)。6. 常見問題速查與避坑心得6.1 我回測的收益曲線為什么看起來太完美如果某一天你的賬戶凈值曲線近乎直線向上沒有明顯回撤先別高興大概率是代碼里有未來函數(shù)。常見原因是回測代碼里用了整段數(shù)據(jù)的統(tǒng)計量比如用了全量價格的最高點作為止盈目標(biāo)或者用了未來數(shù)據(jù)計算指標(biāo)??梢园奄I賣點逐一核對確保策略只在當(dāng)前時刻能看到的信息上做決策。6.2 回測結(jié)果為什么跟實盤差異巨大回測和實盤的差異來源主要有三個手續(xù)費(fèi)沒配全、滑點沒考慮、成交價假設(shè)和實際不同。證券交易除了傭金還有印花稅、過戶費(fèi)如果策略交易頻率高這些費(fèi)用會大大影響最終收益。另外實盤中大單買入時會推動價格上漲你能成交的價格往往比當(dāng)前價格略差這就是滑點。Backtrader支持設(shè)置滑點建議在更接近實際情況的假設(shè)下再評估策略。6.3 數(shù)據(jù)源報錯、日期不對、數(shù)據(jù)缺失怎么辦遇到數(shù)據(jù)問題先打印數(shù)據(jù)范圍做檢查data bt.feeds.PandasData(datanamedf) print(data.fromdate, data.todate) print(len(data))如果數(shù)據(jù)量和預(yù)期不一致多半是DataFrame里有空行或者索引不是按時間順序排列的。處理辦法是先做好數(shù)據(jù)清理去重、排序、填充缺失值這些步驟最好在接入Backtrader之前做干凈。數(shù)據(jù)是回測的地基地基不牢再好的策略都是空中樓閣。6.4 關(guān)于參數(shù)優(yōu)化的最后一點心得我自己踩過最大的坑就是沉迷參數(shù)優(yōu)化恨不得把均線周期、止損比例、倉位大小全部調(diào)到收益最大再上實盤結(jié)果實盤表現(xiàn)遠(yuǎn)不如回測。后來我養(yǎng)成了一個習(xí)慣每優(yōu)化一組參數(shù)就用另一段時間的數(shù)據(jù)做驗證并且接受收益回落到七成這個現(xiàn)實。畢竟回測是在歷史地圖上找寶藏實盤是走進(jìn)迷霧里尋路二者本來就不是同一件事。Backtrader這套框架你越用越會發(fā)現(xiàn)它值得深挖的地方實在太多了。除了SMA和CrossOver這些基礎(chǔ)指標(biāo)你還能在策略里加自定義止損、動態(tài)倉位、多空雙向交易除了回測你還能用Cerebro的runlive()接入實盤數(shù)據(jù)源做紙面交易測試。對個人玩家來說Backtrader完全可以覆蓋從點子到驗證再到模擬交易的完整鏈路。我個人的建議是不要一上來就追求復(fù)雜的策略和高級的功能先把雙均線這類最簡單的策略跑透搞清楚每個指標(biāo)怎么讀、每筆交易怎么記錄再慢慢往上疊加規(guī)則。回測工具只是工具箱里的一把扳手真正的盈利能力來自你對市場、對規(guī)則、對風(fēng)險的持續(xù)理解。本文還有配套的精品資源點擊獲取