Backtrader现实咬合

  |  

上一篇文章设法拷贝了该BTFD 策略,发现真正的收益 16x 而不是 31x

但正如拷贝期间所指出的:

  • 不收取佣金

  • 使用2x 杠杆不收取利息

这就提出了一个显而易见的问题:

  • 当收取佣金和利息时,这16x中有多少会存在?

幸运的是,前面的示例足够灵活,可以对其进行试验。为了有一些视觉反馈和验证,下面的代码将被添加到策略中

def start(self):
    print(','.join(['TRADE', 'STATUS', 'Value', 'PNL', 'COMMISSION']))

def notify_order(self, order):
    if order.status in [order.Margin]:
        print('ORDER FAILED with status:', order.getstatusname())

def notify_trade(self, trade):
    if trade.isclosed:
        print(','.join(map(str, [
            'TRADE', 'CLOSE',
            self.data.num2date(trade.dtclose).date().isoformat(),
            trade.value,
            trade.pnl,
            trade.commission,
        ]
        )))
    elif trade.justopened:
        print(','.join(map(str, [
            'TRADE', 'OPEN',
            self.data.num2date(trade.dtopen).date().isoformat(),
            trade.value,
            trade.pnl,
            trade.commission,
        ]
        )))

这一切都与以下内容有关:

  • 查看交易的开仓和收盘方式(价值、损益、价值和佣金)

  • 如果订单因资金不足而被拒绝Margin ,则提供反馈

    注意

    由于投资金额会有调整,留有佣金空间,有些订单无法被经纪人接受。这种视觉反馈可以识别情况

验证

首先进行快速测试,看看有些订单不被接受。

$ ./btfd.py --comminfo commission=0.001,leverage=2.0 --strat target=1.0

TRADE,STATUS,Value,PNL,COMMISSION
ORDER FAILED with status: Margin
ORDER FAILED with status: Margin
TRADE,OPEN,1990-01-08,199345.2,0.0,199.3452
TRADE,CLOSE,1990-01-10,0.0,-1460.28,397.23012

通知:

  • 我们申请target=1.0 的意思是:尝试投资100%的资本。这是缺省设置,但它作为参考。

  • commission=0.0010.1% 确保我们有时会达到保证金

  • 第1 订单被拒绝 Margin

  • 接受第3个 订单。这不是错误。系统试图投资 100% 资本,但资产有一个价格,这被用来计算赌注的大小。大小从计算实际可用现金的潜在大小的实际结果向下舍入。这种四舍五入为这个3rd 订单的佣金留下了足够的空间。

  • 交易通知 (OPENCLOSE) 显示开盘佣金和最终总佣金,以及close200k的值,显示2x实际杠杆。

    开仓佣金是199.34520.1%杠杆价值的佣金,即:199,345.2

其余的测试将确保target=0.99xx为选定的佣金提供足够的空间。

现实咬合

让我们来看看一些真实的例子

目标 99.8% - 佣金 0.1%

./btfd.py --comminfo commission=0.001,leverage=2.0 --strat target=0.998 --plot

起泡的藤壶!!!该BTFD策略不仅绝不会close16x收益:它损失了大部分资金

  • 100,000 下到大致 4,027

注意

降值是非杠杆值,因为这是平仓时将返回系统的近似值

目标 99.9% - 佣金 0.05%

很可能是因为委员会过于激进。让我们去一半

./btfd.py --comminfo commission=0.0005,leverage=2.0 --strat target=0.999 --plot

不,不。佣金没有那么激进,因为系统仍然亏损,从 100,000 低到左右 69,000 (非杠杆价值)

目标 99.95% - 佣金 0.025%

佣金再次除以二

./btfd.py --comminfo commission=0.00025,leverage=2.0 --strat target=0.9995 --plot

最后,系统赚钱了:

  • 初始100,000值用于331,4593x收益。

  • 但这与资产的性能不匹配,资产的性能已经超过600k

注意

样本接受--fromdate YYYY-MM-DD 并选择 --todate YYYY-MM-DD 策略必须应用于哪个时期。这将允许针对不同的日期范围测试类似的方案。

结论

16x 面临佣金时,收益并不成立。对于一些经纪人提供的佣金(没有上限和基于%的),人们需要一个非常好的交易来确保系统赚钱。

在这种情况下,策略应用于,S&P500策略 BTFD 与指数的表现不匹配。

没有采用利率。使用佣金足以看到与任何潜在利润的距离16x 有多远。无论如何,利率的 2% 运行将像这样运行

./btfd.py --comminfo commission=0.00025,leverage=2.0,interest=0.02,interest_long=True --strat target=0.9995 --plot

interest_long=True 是必需的,因为收取利息的默认行为是仅对空头头寸运行此操作

示例用法

$ ./btfd.py --help
usage: btfd.py [-h] [--offline] [--data TICKER]
               [--fromdate YYYY-MM-DD[THH:MM:SS]]
               [--todate YYYY-MM-DD[THH:MM:SS]] [--cerebro kwargs]
               [--broker kwargs] [--valobserver kwargs] [--strat kwargs]
               [--comminfo kwargs] [--plot [kwargs]]

BTFD - http://dark-bid.com/BTFD-only-strategy-that-matters.html - https://www.
reddit.com/r/algotrading/comments/5jez2b/can_anyone_replicate_this_strategy/

optional arguments:
  -h, --help            show this help message and exit
  --offline             Use offline file with ticker name (default: False)
  --data TICKER         Yahoo ticker to download (default: ^GSPC)
  --fromdate YYYY-MM-DD[THH:MM:SS]
                        Starting date[time] (default: 1990-01-01)
  --todate YYYY-MM-DD[THH:MM:SS]
                        Ending date[time] (default: 2016-10-01)
  --cerebro kwargs      kwargs in key=value format (default: stdstats=False)
  --broker kwargs       kwargs in key=value format (default: cash=100000.0,
                        coc=True)
  --valobserver kwargs  kwargs in key=value format (default:
                        assetstart=100000.0)
  --strat kwargs        kwargs in key=value format (default:
                        approach="highlow")
  --comminfo kwargs     kwargs in key=value format (default: leverage=2.0)
  --plot [kwargs]       kwargs in key=value format (default: )

示例代码

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

# References:
#  - https://www.reddit.com/r/algotrading/comments/5jez2b/can_anyone_replicate_this_strategy/
#  - http://dark-bid.com/BTFD-only-strategy-that-matters.html

import argparse
import datetime

import backtrader as bt


class ValueUnlever(bt.observers.Value):
    '''Extension of regular Value observer to add leveraged view'''
    lines = ('value_lever', 'asset')
    params = (('assetstart', 100000.0), ('lever', True),)

    def next(self):
        super(ValueUnlever, self).next()
        if self.p.lever:
            self.lines.value_lever[0] = self._owner.broker._valuelever

        if len(self) == 1:
            self.lines.asset[0] = self.p.assetstart
        else:
            change = self.data[0] / self.data[-1]
            self.lines.asset[0] = change * self.lines.asset[-1]


class St(bt.Strategy):
    params = (
        ('fall', -0.01),
        ('hold', 2),
        ('approach', 'highlow'),
        ('target', 1.0)
    )

    def __init__(self):
        if self.p.approach == 'closeclose':
            self.pctdown = self.data.close / self.data.close(-1) - 1.0
        elif self.p.approach == 'openclose':
            self.pctdown = self.data.close / self.data.open - 1.0
        elif self.p.approach == 'highclose':
            self.pctdown = self.data.close / self.data.high - 1.0
        elif self.p.approach == 'highlow':
            self.pctdown = self.data.low / self.data.high - 1.0

    def next(self):
        if self.position:
            if len(self) == self.barexit:
                self.close()
        else:
            if self.pctdown <= self.p.fall:
                self.order_target_percent(target=self.p.target)
                self.barexit = len(self) + self.p.hold

    def start(self):
        print(','.join(['TRADE', 'STATUS', 'Value', 'PNL', 'COMMISSION']))

    def notify_order(self, order):
        if order.status in [order.Margin, order.Rejected, order.Canceled]:
            print('ORDER FAILED with status:', order.getstatusname())

    def notify_trade(self, trade):
        if trade.isclosed:
            print(','.join(map(str, [
                'TRADE', 'CLOSE',
                self.data.num2date(trade.dtclose).date().isoformat(),
                trade.value,
                trade.pnl,
                trade.commission,
            ]
            )))
        elif trade.justopened:
            print(','.join(map(str, [
                'TRADE', 'OPEN',
                self.data.num2date(trade.dtopen).date().isoformat(),
                trade.value,
                trade.pnl,
                trade.commission,
            ]
            )))


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']):
        kwargs[d] = datetime.datetime.strptime(a, dtfmt + tmfmt * ('T' in a))

    if not args.offline:
        YahooData = bt.feeds.YahooFinanceData
    else:
        YahooData = bt.feeds.YahooFinanceCSVData

    # Data feed - no plot - observer will do the job
    data = YahooData(dataname=args.data, plot=False, **kwargs)
    cerebro.adddata(data)

    # Broker
    cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

    # Add a commission
    cerebro.broker.setcommission(**eval('dict(' + args.comminfo + ')'))

    # Strategy
    cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

    # Add specific observer
    cerebro.addobserver(ValueUnlever, **eval('dict(' + args.valobserver + ')'))

    # 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=(' - '.join([
            'BTFD',
            'http://dark-bid.com/BTFD-only-strategy-that-matters.html',
            ('https://www.reddit.com/r/algotrading/comments/5jez2b/'
             'can_anyone_replicate_this_strategy/')]))
        )

    parser.add_argument('--offline', required=False, action='store_true',
                        help='Use offline file with ticker name')

    parser.add_argument('--data', required=False, default='^GSPC',
                        metavar='TICKER', help='Yahoo ticker to download')

    parser.add_argument('--fromdate', required=False, default='1990-01-01',
                        metavar='YYYY-MM-DD[THH:MM:SS]',
                        help='Starting date[time]')

    parser.add_argument('--todate', required=False, default='2016-10-01',
                        metavar='YYYY-MM-DD[THH:MM:SS]',
                        help='Ending date[time]')

    parser.add_argument('--cerebro', required=False, default='stdstats=False',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--broker', required=False,
                        default='cash=100000.0, coc=True',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--valobserver', required=False,
                        default='assetstart=100000.0',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--strat', required=False,
                        default='approach="highlow"',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--comminfo', required=False, default='leverage=2.0',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--plot', required=False, default='',
                        nargs='?', const='volume=False',
                        metavar='kwargs', help='kwargs in key=value format')

    return parser.parse_args(pargs)


if __name__ == '__main__':
    runstrat()

推荐阅读

相关文章

Backtrader教程:经纪人 - 开仓作弊

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

Backtrader期货补偿与现货补偿

版本1.9.32.116 增加了对社区中呈现的有趣用例 的支持 以期货开始交易,包括实物交割 让一个指针告诉你一些事情 如果需要, close 现货价格操作,有效地取消实物交割,无论是为了接收货物还是为了必须交付货物(并希望获利)来头寸。

Backtrader标杆

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

BacktraderPyFolio 集成

注意 2017年2月 pyfolio API 已更改,不再 create_full_tear_sheet 具有 gross_lev 作为命名参数的参数。

Backtrader教程:过滤器 - 参考

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

Backtrader跨越数字

《backtrader》的发布1.9.27.105纠正了一个疏忽。这是一个疏忽,因为拼图的所有部分都已到位,但启动并不是在所有角落都进行的。 该机制使用一个名为的属性_mindatas,因此让我们将其称为: mindatas。 社区问了这个问题,答案并不是很到位。

Backtrader混合时间帧

1.3.0.92版本带来了混合来自不同时间帧的数据(来自 data feeds 和/或指针)的可能性。 到版本:https://github.com/mementum/backtrader/发布/标签/1.3.0.92 背景:指示器是智能哑对象。 他们很聪明,因为他们可以进行复杂的计算。

Backtrader教程:数据馈送 - 熊猫

注意 pandas 并且必须安装其依赖项 支持Pandas Dataframes似乎受到很多人的关注,他们依赖于已经可用的解析代码来分析不同的数据源(包括CSV)和Pandas提供的其他功能。 数据馈送的重要声明。 注意 这些只是 声明。不要盲目拷贝此代码。

Backtrader现实咬合

上一篇文章设法拷贝了该BTFD 策略,发现真正的收益 16x 而不是 31x。 但正如拷贝期间所指出的: 不收取佣金 使用2x 杠杆不收取利息 这就提出了一个显而易见的问题: 当收取佣金和利息时,这16x中有多少会存在? 幸运的是,前面的示例足够灵活,可以对其进行试验。

Backtrader信贷利息

在某些情况下,真实经纪人的现金金额可能会减少,因为资产操作包括利率。例子: 卖空股票 交易所买卖基金包括多头和空头 这意味着不仅交易构成了系统的盈利能力,因为信贷上的利息在帐户上佔有一席之地。 为了涵盖这种情况, backtrader 包括(从发佈1.8.8.96开始)功能来考虑这一点。