已經有一段時間了, backtrader已經在使用,可以說,專業,除了backtrader一些銀行和貿易公司的已知用途,用於Backtrader基金。
歷史
一群志同道合且相識已久的人決定走上開設(對沖)基金的道路,並以反向交易者為交易理念的基石。有一件事是不能放棄的:它必須受到 100% 的監管(但不是在開曼群島或類似的地方)
位置、遺產和網絡首先將重點放在歐盟,然後是西班牙,那裡(與其他一些地方一樣)立法允許 Umbrella Funds 託管子基金,從而能夠以更少的資金和數量創建一個完全受監管的基金的參與者。
並且……該基金已獲得西班牙監管機構 CNMV (Comisión Nacional del Mercado de Valores) 的批准,其 ISIN 為: ES0131444038
。鏈接:
對於那些能夠閱讀西班牙語的人, backtrader的使用記錄在官方基金傳單中。
對於那些可能在某個時候決定繼續前進的人來說,最重要的事情是:
官僚作風緩慢,沿途會有很多問題
跟踪一切(執行的操作、現金/淨資產價值水平、頭寸、槓桿)
必須向監管機構報告(因此需要收集並妥善組織上述信息)
保持在定義的風險/波動水平內不僅僅是一個指導方針
管理 OPM(別人的錢)是一個真正的心理負擔。會有損失,也會有問題。無論這些問題的意圖和天真程度如何:它們都會產生影響。
backtrader是交易理念的基礎,它發現了一個新的應用領域:報告。用於控制風險/波動性的自定義分析器和指標減輕了管理負擔。
可能是因為我們老了(而且過時了),我們仍然更喜歡手動執行(自動執行將在未來的某個時候接管)
下面描述的功能是為了幫助管理基金和回測資金進出和績效不再是跟踪資產淨值的問題。
在版本1.9.52.121
中, backtrader中的經紀人不僅以現金/價值形式跟踪會計,而且還像在基金中一樣,即:
基金價值(實際基金份額)
股份數量
有了這個,人們實際上可以模擬現金存款和現金提款,同時仍然跟踪實際業績,在常規會計中,這會被現金流入/流出所扭曲。
除了經紀商的變化之外,分析器和觀察器(那些對資產淨值做一些事情的人)也進行了調整,以支持fund
參數,以決定實際應該跟踪什麼。例如TimeReturn
:
... cerebro.addanalyzer(bt.analyzers.TimeReturn) # auto-detect broker mode cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True) # track fund value cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False) # track net asset value ...
這個基金追踪的是什麼?
想像一個用例(稍後在示例中),某人以1000
貨幣單位開始,並在每個月的 15日增加100
貨幣單位。 12 個月後,帳戶中的總數為2200
。根據最初持倉計算的回報
以通常的方式計算回報,這意味著在沒有執行單一操作的情況下,年度回報的表現是: 120%
。當然,這是不對的。
為了緩解這個問題,無論賬戶的初始值如何,基金份額的價值(fundvalue)都設置為100.0
。有了這個和初始資產淨值( 1000
貨幣單位),可以計算出基金份額的數量為
fundshares = net-asset-value / fundvalue
在這種情況下是1000 / 100.0 = 10 shares
每次增加現金,我們都會增加股票數量:
-
new_fund_shares = cash_addition / fundvalue
因為我們添加了100.0
個貨幣單位並且沒有執行任何操作:
- ``100.0 / 100.0 = 1 share``
請注意,基金價值保持不變。快速轉發到年底,我們有以下內容:
起始淨資產值:
1000
最終資產淨值:
2200
起始基金價值 =
100
最終基金價值 =
100
起始股數:
10
最終股數:
22
現在,如果我們使用開始和結束基金價值來計算回報,並且因為它們是相同的,我們有一個: 0%
,這與現實相符。因為現金增加沒有改變
在backtrader中使用資金跟踪
添加現金
首先,經紀人獲得了一種向系統規範添加現金的方法:
add_cash(cash)
例如在策略中使用它:
def next(self): if whatever: self.broker.add_cash(1000.0)
此方法必須用於跟踪現金進入和退出系統並正確跟踪基金價值。
自動的
在代理中激活它:
... cerebro.broker.set_fundmode(True) ...
同時更改默認基金起始值:
... cerebro.broker.set_fundmode(True, 10.0) # the default is 100 ...
或在獨立通話中:
... cerebro.broker.set_fundmode(True) cerebro.broker.set_fundstartval(10.0) # the default is 100 ...
激活默認模式並從上面返回TimeReturn
分析器示例後:
... # 1 cerebro.addanalyzer(bt.analyzers.TimeReturn) # auto-detect broker mode # 2 cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True) # track fund value # 3 cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False) # track net asset value ...
1
和2
是等價的。但是應該選擇1
。如果希望進行比較,可以始終強制TimeReturn
分析器不使用基金價值,而是跟踪淨資產價值
一個例子值一千字。在示例中,我們將使用如上所述的操作,但有一些額外的現金(資產的每股價值超過3000
)。初始現金水平為10000
,這是backtrader的默認值,每月 15日,將添加1000
額外貨幣單位(使用循環Timer
)。這將是 24 個月(這是backtrader中使用的標準數據樣本的大小)
無需任何操作
$ ./fund-tracker.py --broker fundmode=True --strat cash2add=1000 --cerebro writer=True --plot
圖形視圖
和文本輸出(為可讀性而設):
- timereturn: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... - fund: None ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 0.0 - 2006-12-31: 0.0 ....................................................................... - timereturn1: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Params: ... - fund: True ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 0.0 - 2006-12-31: 0.0 ....................................................................... - timereturn2: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... - fund: False ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 1.2 - 2006-12-31: 0.545454545455
添加了 3 個TimeReturn
分析器。
第一個有
fund=None
(默認),這意味著跟踪fundmode
(在這種情況下為True
)它說年度回報是
0.0
和0.0
。由於我們沒有進行任何操作:好的第二個有
fund= True
,這意味著總是使用fundvalue它說年度回報是
0.0
和0.0
。由於我們沒有進行任何操作:好的第三個有
fund= False
,這意味著總是使用淨資產值它表示年回報率為
1.2
(120%) 和0.54
(54%)。由於我們沒有進行任何操作:這顯然是錯誤的
該圖還包含 2 個新的觀察者( FundValue
和FundShares
),它們可以查看即使資產淨值隨著每月現金的增加而增長,基金價值如何保持恆定為100.0
。同時,股票隨著每次現金增加而增長。
我們做個交易吧
與上述相同,但有些交易使用標準移動平均線交叉
$ ./fund-tracker.py --broker fundmode=True --strat cash2add=1000,trade=True --cerebro writer=True --plot
圖形視圖
和文本輸出(為可讀性而設):
- timereturn: ... - fund: None ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: -0.00642229824537 - 2006-12-31: 7.78998679263e-05 ....................................................................... - timereturn1: ... - fund: True ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: -0.00642229824537 - 2006-12-31: 7.78998679263e-05 ....................................................................... - timereturn2: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... - fund: False ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 1.19378185337 - 2006-12-31: 0.546479045423
與之前相同的三個TimeReturn
分析器。使用fund=None
和fund= True
的結果合理,而使用fund= False
的結果則以119%
和54%
明顯不在圖表之列,這顯然不是移動平均線交叉提供的回報。
手動的
在這種情況下(這是經紀商的默認設置,即使經紀商正在跟踪基金的價值,也只有fund= True
的分析器才會使用該值。
僅使用文本輸出的快速運行:
$ ./fund-tracker.py --strat cash2add=1000,trade=True --cerebro writer=True
輸出:
- timereturn: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... - fund: None ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 1.19378185337 - 2006-12-31: 0.546479045423 ....................................................................... - timereturn1: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... - fund: True ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: -0.00642229824537 - 2006-12-31: 7.78998679263e-05 ....................................................................... - timereturn2: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... - fund: False ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - 2005-12-31: 1.19378185337 - 2006-12-31: 0.546479045423
現在只有fund= True
的TimeReturn
提供合理的結果。
結論
在經紀人中實施的新fundmode
模式可以(自動/手動)在分析器中使用,允許使用反向交易者來模擬真實基金的內部運作或用例,例如在給定的時間間隔內持續投資資金。
示例使用
$ ./fund-tracker.py --help usage: fund-tracker.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] Fund Tracking Sample optional arguments: -h, --help show this help message and exit --data0 DATA0 Data to read in (default: ../../datas/2005-2006-day-001.txt) --fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --cerebro kwargs kwargs in key=value format (default: ) --broker kwargs kwargs in key=value format (default: ) --sizer kwargs kwargs in key=value format (default: ) --strat kwargs kwargs in key=value format (default: ) --plot [kwargs] kwargs in key=value format (default: )
示例代碼
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import backtrader as bt class St(bt.SignalStrategy): params = dict( cash2add=None, cashonday=15, pfast=10, pslow=30, trade=False, ) def __init__(self): self.add_timer(when=bt.Timer.SESSION_END, monthdays=[self.p.cashonday]) sma1 = bt.ind.SMA(period=self.p.pfast) sma2 = bt.ind.SMA(period=self.p.pslow) signal = bt.ind.CrossOver(sma1, sma2) if self.p.trade: self.signal_add(bt.SIGNAL_LONGSHORT, signal) def notify_timer(self, timer, when, *args, **kwargs): # no need to check the timer, there is only one if self.p.cash2add is not None: self.broker.add_cash(self.p.cash2add) def next(self): pass def runstrat(args=None): args = parse_args(args) cerebro = bt.Cerebro() # Data feed kwargs kwargs = dict() # Parse from/to-date dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S' for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']): if a: strpfmt = dtfmt + tmfmt * ('T' in a) kwargs[d] = datetime.datetime.strptime(a, strpfmt) data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs) cerebro.adddata(data0) # Broker cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')')) # Sizer cerebro.addsizer(bt.sizers.PercentSizer, **eval('dict(' + args.sizer + ')')) # Strategy cerebro.addstrategy(St, **eval('dict(' + args.strat + ')')) cerebro.addobserver(bt.observers.FundValue) cerebro.addobserver(bt.observers.FundShares) ankwargs = dict(timeframe=bt.TimeFrame.Years) cerebro.addanalyzer(bt.analyzers.TimeReturn, **ankwargs) cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True, **ankwargs) cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False, **ankwargs) # Execute cerebro.run(**eval('dict(' + args.cerebro + ')')) if args.plot: # Plot if requested to cerebro.plot(**eval('dict(' + args.plot + ')')) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=( 'Fund Tracking Sample' ) ) parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt', required=False, help='Data to read in') # Defaults for dates parser.add_argument('--fromdate', required=False, default='', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--todate', required=False, default='', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--cerebro', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--broker', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--sizer', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--strat', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--plot', required=False, default='', nargs='?', const='{}', metavar='kwargs', help='kwargs in key=value format') return parser.parse_args(pargs) if __name__ == '__main__': runstrat()