BTFD - Reality Bites

The previous post managed to replicate the `BTFD` strategy, finding out that the real gains were `16x` rather than `31x`.

But as pointed out during the replication:

• No commission was charged

• No interest was charged for using a `2x` leverage

And that raises the obvious question:

• How much of that 16x will be there when commission and interest are charged?

Luckily the previous sample is flexible enough to experiment with it. To have some visual feedback and verification, the following code will be added to the strategy

```def start(self):

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

print(','.join(map(str, [
]
)))
print(','.join(map(str, [
]
)))
```

• Seeing how trades are opened and closed (value, profit and loss, value and commission)

• Providing feedback if an order is being rejected with `Margin` due to insufficient funds

Note

Because there will be an adjustment of the amount of money to invest, to leave room for commission, some orders could not be accepted by the broker. This visual feedback allows identifying the situation

Verification

First a quick test to see that some orders are not accepted.

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

ORDER FAILED with status: Margin
ORDER FAILED with status: Margin
```

Notice:

• We apply `target=1.0` which means: try to invest 100% of the capital. This is the default, but it is there as a reference.

• `commission=0.001` or `0.1%` to ensure we will sometimes meet the margin

• The 1st two orders are rejected with `Margin`

• The 3rd order is accepted. This is not an error. The system tries to invest `100%` of the capital, but the asset has a price and this is used to calculate the size of the stake. Size is rounded down from the actual result of calculating the potential size from the actual available cash. This rounding down has left room enough for the commission with this 3rd order.

• The trade notifications (`OPEN` and `CLOSE`) show the opening commission and the final total commission an the value which is close to `200k`, showing the `2x` leverage in action.

The opening commission is `199.3452` which is `0.1%` of the leveraged value which is: `199,345.2`

The remaining tests will be made with `target=0.99x` where `x` will ensure room enough for the selected commission.

Reality Bites

Let’s go for some real examples

Target 99.8% - Commission 0.1%

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

Blistering Barnacles!!! Not only is the `BTFD` strategy by no means close to the `16x` gains: IT LOSES MOST OF THE MONEY.

• From `100,000` down to roughly `4,027`

Note

The down to value is the non-leveraged value, because this is the approximate value that will be back in the system when the position is closed

Target 99.9% - Commission 0.05%

It may well have been that the commission is too aggressive. Let’s go for half of it

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

NO, NO. The commission was not that aggressive, because the system still loses money, going down from `100,000` down to around `69,000` (the non-leverage value)

Target 99.95% - Commission 0.025%

Commission is divided by two again

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

Finally the system makes money:

• The initial `100,000` are taken up to `331,459` for `3x` gains.

• But this doesn’t match the performance of the asset which has gone up to over `600k`

Note

The sample accepts `--fromdate YYYY-MM-DD` and `--todate YYYY-MM-DD` to select to which period the strategy has to be applied. This would allow testing similar scenarios for different date ranges.

Conclusion

The `16x` gains do not hold when confronted with commission. For the commission offered by some brokers (no cap and %-based) one would need a very good deal to make sure the system makes money.

And in this case in which the strategy is applied to the `S&P500`, the `BTFD` strategy doesn’t match the performance of the index.

No interest rate has been applied. Using commissions is enough to see how far away is `16x` from any potential profits. In any case, a run with a `2%` interest rate would be executed like this

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

`interest_long=True` is needed, because the default behavior for charging interest is to do it only for short positions

Sample usage

```\$ ./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.

optional arguments:
-h, --help            show this help message and exit
--offline             Use offline file with ticker name (default: False)
--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: )
```

Sample Code

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

# References:
#  - http://dark-bid.com/BTFD-only-strategy-that-matters.html

import argparse
import datetime

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):

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

print(','.join(map(str, [
]
)))
print(','.join(map(str, [
]
)))

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)

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

cerebro.broker.setcommission(**eval('dict(' + args.comminfo + ')'))

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

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',
'can_anyone_replicate_this_strategy/')]))
)

help='Use offline file with ticker name')

metavar='YYYY-MM-DD[THH:MM:SS]',
help='Starting date[time]')

metavar='YYYY-MM-DD[THH:MM:SS]',
help='Ending date[time]')

metavar='kwargs', help='kwargs in key=value format')

default='cash=100000.0, coc=True',
metavar='kwargs', help='kwargs in key=value format')

default='assetstart=100000.0',
metavar='kwargs', help='kwargs in key=value format')

default='approach="highlow"',
metavar='kwargs', help='kwargs in key=value format')