上一篇文章設法複製了該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.001
或0.1%
確保我們有時會達到保證金 -
第1個 訂單被拒絕
Margin
-
接受第3個 訂單。這不是錯誤。系統試圖投資
100%
資本,但資產有一個價格,這被用來計算賭注的大小。大小從計算實際可用現金的潛在大小的實際結果向下捨入。這種四捨五入為這個3rd 訂單的傭金留下了足夠的空間。 -
交易通知 (
OPEN
和CLOSE
) 顯示開盤傭金和最終總傭金,以及close200k
的值,顯示2x
實際槓桿。開倉傭金是
199.3452
0.1%
槓桿價值的傭金,即:199,345.2
其餘的測試將確保target=0.99x
x
為選定的傭金提供足夠的空間。
現實咬合
讓我們來看看一些真實的例子
目標 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,459
3x
收益。 -
但這與資產的性能不匹配,資產的性能已經超過
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()