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對逐筆報價數據重新取樣

backtrader 已經可以從分鐘數據中重新採樣。接受價格變動數據不是問題,只需將 4 個常用欄位(open、 high、 low、 close)設置為價格變動值。 但是傳遞要重新採樣的逐筆報價數據再次生成相同的數據。作為或版本 1.1.11.88,情況已不再如此。

Backtrader期貨補償與現貨補償

版本1.9.32.116 增加了對社區中呈現的有趣用例 的支援 以期貨開始交易,包括實物交割 讓一個指標告訴你一些事情 如果需要, close 現貨價格操作,有效地取消實物交割,無論是為了接收貨物還是為了必須交付貨物(並希望獲利)來頭寸。

BacktraderPyFolio 集成

注意 2017年2月 pyfolio API 已更改,不再 create_full_tear_sheet 具有 gross_lev 作為命名參數的參數。

Backtrader教程:過濾器 - 參考

工作階段篩檢程式 類 backtrader.filters。

Backtrader數據多時間幀

有時投資決策是使用不同的時間框架做出的: 每周評估趨勢 每天執行條目 或者5分鐘對60分鐘。 這意味著需要將多個時間幀的數據組合在 backtrader 中以支援此類組合。 對它的本機支持已經內置。

Backtrader智慧質押

版本 1.6.4.93 標誌著 backtrader 的一個重要里程碑,即使版本號的更改很小。 職位大小調整是閱讀Van K. Tharp的《Trade Your Way To Financial Freedom 》後,為這個專案奠定基礎的事情之一。

Backtrader交叉回溯測試陷阱

在backtrader 社區中 ,傾向於重複的事情是,用戶解釋了複製在例如 TradingView 中獲得的回溯測試結果的意願,這些天非常流行,或者其他一些回溯測試平臺。

Backtrader蟒蛇隱藏的力量3

Last,但並非最不重要的一點是,在這個系列中,關於如何在 backtrader 中使用Python的隱藏功能是一些神奇變數是如何出現的。

Backtrader多數據範例

社區中的幾個主題似乎以如何跟蹤訂單為導向,特別是當幾個data feeds在起作用時,還包括當多個訂單一起工作時,