Backtrader基金追踪

  |  

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

12是等价的。但是应该选择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.00.0 。由于我们没有进行任何操作:好的

  • 第二个有fund= True ,这意味着总是使用fundvalue

    它说年度回报是0.00.0 。由于我们没有进行任何操作:好的

  • 第三个有fund= False ,这意味着总是使用净资产值

    它表示年回报率为1.2 (120%) 和0.54 (54%)。由于我们没有进行任何操作:这显然是错误的

该图还包含 2 个新的观察者FundValueFundShares ),它们可以查看即使资产净值随着每月现金的增加而增长,基金价值如何保持恒定为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=Nonefund= 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= TrueTimeReturn提供合理的结果。

结论

在经纪人中实施的新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()

推荐阅读

相关文章

Backtrader 教程:绘图 - 同一轴

上一篇future-spot 将原始数据和稍微(随机)修改的数据绘制在同一空间上,但不在同一轴上。从该帖子中恢复第一张图片。有人能看见:图表左右两侧有不同的刻度当查看在原始数据周围振荡+- 50点的摆动红线(随机数据)时,这一点最为明显。在图表上,视觉印像是这些随机数据大多总是高于原始数据。

Backtrader标杆

backtrader 包括 2 种不同类型的对象,可帮助进行跟踪: Observers Analyzers 工单 #89 是关于添加资产基准测试的。明智的是,人们实际上可能有一个策略,即使积极,也低于简单地跟踪资产所能提供的策略。

Backtrader逃离 OHLC 土地

在backtrader的概念和开发过程中应用的关键概念之一是灵活性。 Python 的元编程和自省功能曾经是(现在仍然是)保持许多东西灵活同时仍然能够交付的基础。一篇旧帖子显示了扩展概念。

Backtrader回溯

在一些关于改进的ShapeRatio的提示之后, backtrader 已将此分析仪添加到其武器库中。 文献位于: 从对数回报的好处开始,并遵循在SharpeRatio方程的分母中具有标准偏差的副作用,本文档开发了该分析仪的公式和期望。

Backtrader扩展数据馈送

GitHub 中的问题实际上是在推动文档部分的完成,或者说明我了解 backtrader 是否具有我从最初时刻就设想的易用性和灵活性以及在此过程中做出的决定。 在本例中为问题 #9。

Backtrader教程:过滤器 - 参考

工作阶段筛检程序 类 backtrader.filters。

Backtrader python 隐藏的细节

只有当遇到 backtrader 的真实用户时,人们才能意识到平台中使用的抽象和Python功能是否有意义。 在不撇开python的座右铭的情况下, backtrader 试图为用户提供尽可能多的控制权,同时通过将Python提供的隐藏功能付诸行动来简化使用。 第一个示例是系列文章的第一篇。

Backtrader交叉回溯测试陷阱

在backtrader 社区中 ,倾向于重复的事情是,用户解释了拷贝在例如 TradingView 中获得的回溯测试结果的意愿,这些天非常流行,或者其他一些回溯测试平台。

Backtrader教程:佣金计划 - 信贷利息

在某些情况下,真实经纪人的现金金额可能会减少,因为资产操作包括利率。例子: 卖空股票 交易所买卖基金包括多头和空头 该费用直接与经纪人帐户中的现金余额挂钩。但它仍然可以被视为佣金计划的一部分。因此,它已被建模为 backtrader。

Backtrader开场作弊

“发布”1.9.44.116 添加了对 Cheat-On-Open的支持。这似乎是那些全力以赴的人的需求功能,他们在酒吧 close 后进行了计算,但希望与 open 价格相匹配。 当开盘价跳空(上涨或下跌,取决于是否buysell有效)并且现金不足以进行全面运营时,这样的用例就会失败。这将强制代理拒绝该操作。