已经有一段时间了, 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()