MACD Settings
In the Algotrading site of Reddit a thread about optimizing MACD settings was found.
Having started the backtrader quest because of Trading Your Way To Financial Freedom - Amazon Link I had no other choice but to post an answer and craft a sample.
The strategy approach is loosely based on some of the ideas presented in the book. Nothing new under the sun. And the parameters have been quickly set. No overfitting, no optimization, no nothing. Roughly:
-
Enter if the
macd
line crosses thesignal
line to the upside and a controlSimple Moving Average
has had a net negative direction in the last x periods (current SMA value below the value x periods ago) -
Set a
stop
priceN x ATR
times away from theclose
price -
Exit the market if the
close
price goes below thestop
price -
If still in the market, update the
stop
price only if it’s greater than the actual one
The staking is done by:
- Having a
Sizer
allocate a percentage of the available cash to the operation. The more the strategy wins, the more it stakes … and the more it loses the less it stakes.
There is a commision included. Because the tests will be done with stocks a
percentage commission has been chosen with a value 0.0033
(aka 0.33%
per round trip.
To put this into play, there will be 3 runs with 3 datas (for a total of 9 runs)
-
The standard parameters which have been chosen manually
-
Increase cash allocation perentage from
0.20
to0.50
-
Increase ATR distance for the stop from
3.0
to4.0
to try to avoid being whipped
The manually chosen parameters for the strategy:
params = (
# Standard MACD Parameters
('macd1', 12),
('macd2', 26),
('macdsig', 9),
('atrperiod', 14), # ATR Period (standard)
('atrdist', 3.0), # ATR distance for stop price
('smaperiod', 30), # SMA Period (pretty standard)
('dirperiod', 10), # Lookback period to consider SMA trend direction
)
And system wide:
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--cashalloc', required=False, action='store',
type=float, default=0.20,
help=('Perc (abs) of cash to allocate for ops'))
parser.add_argument('--commperc', required=False, action='store',
type=float, default=0.0033,
help=('Perc (abs) commision in each operation. '
'0.001 -> 0.1%%, 0.01 -> 1%%'))
The date range will be from 2005-01-01
to 2014-12-31
for a total of 10
years.
Analyzers
To get some objective data 3 analyzers will be added to the system:
-
Two
TimeReturn
for the entire period-
For the strategy itself
-
For the data being operated upon as benchmark
Effectively benchmarking the strategy against the asset
-
-
A
TimeReturn
to measure annual returns -
A
SharpeRatio
to see how the strategy performs against a risk-free assetThe value is set at
1%
, can be changed with the sample options -
A
SQN
(System Quality Number) to analyze the quality of the trades using this performance indicator defined by Van K. Tharp
Additonally a DrawDown
observer will be added to the mix.
Run 1: Standard Parameters
YHOO
$ ./macdsystem.py --plot --dataset yhoo
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.07118518868
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.316736183525
===============================================================================
TimeReturn:
- 2005-12-31: 0.02323119024
- 2006-12-31: -0.0813678018166
- 2007-12-31: -0.0144802141955
- 2008-12-31: -0.142301023804
- 2009-12-31: 0.0612152927491
- 2010-12-31: 0.00898269987778
- 2011-12-31: -0.00845048588578
- 2012-12-31: 0.0541362123146
- 2013-12-31: 0.0158705967774
- 2014-12-31: 0.0281978956007
===============================================================================
SharpeRatio:
- sharperatio: -0.261214264357
===============================================================================
SQN:
- sqn: -0.784558216044
- trades: 45
ORCL
$ ./macdsystem.py --plot --dataset orcl
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.24890384718
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 2.23991354467
===============================================================================
TimeReturn:
- 2005-12-31: -0.02372911952
- 2006-12-31: 0.0692563226579
- 2007-12-31: 0.0551086853554
- 2008-12-31: -0.026707886256
- 2009-12-31: 0.0786118091383
- 2010-12-31: 0.037571919146
- 2011-12-31: 0.00846519206845
- 2012-12-31: 0.0402937469005
- 2013-12-31: -0.0147124502187
- 2014-12-31: 0.00710131291379
===============================================================================
SharpeRatio:
- sharperatio: 0.359712552054
===============================================================================
SQN:
- sqn: 1.76240859868
- trades: 37
NVDA
$ ./macdsystem.py --plot --dataset nvda
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.0178507058999
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.177604931253
===============================================================================
TimeReturn:
- 2005-12-31: 0.0773031957141
- 2006-12-31: 0.105007457325
- 2007-12-31: 0.015286423657
- 2008-12-31: -0.109130552525
- 2009-12-31: 0.14716076542
- 2010-12-31: -0.0891638005423
- 2011-12-31: -0.0788216550171
- 2012-12-31: -0.0498231953066
- 2013-12-31: -0.0119166712361
- 2014-12-31: 0.00940493597076
===============================================================================
SharpeRatio:
- sharperatio: -0.102967601564
===============================================================================
SQN:
- sqn: -0.0700412395071
- trades: 38
Analysis of Run 1
-
YHOO
The 1st and 2nd
TimeReturn
analyzers (the strategy and the asset itself) show that The strategy has lost7.11%
whilst the asset in question has appreciated31.67%
.Not even worth having a look at the other analyzers
-
ORCL
A
24.89%
for the strategy, but paled by the223.99%
return of the asset itself.The SharpeRatio at
0.35
is still away from the usual minimum target of1
.The SQN returns a
1.76
which at least gets a grading in the Van K. Tharp scale:1.6 - 1.9 Below Average
. -
NVDA
In this case a
-1.78%
for the strategy and a-17.76%
for the asset. The SharpeRatio, with-0.10
shows that even if the strategy has outperformed the asset, it would have been better to go with a1%
bank account.The SQN is obviously not even at the bottom of the scale.
Conclusions of Run 1
- 1 great loss, a tie and a win which underperforms the asset. Not that successful.
Run 2: CashAlloc to 0.50
YHOO
$ ./macdsystem.py --plot --dataset yhoo --cashalloc 0.50
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.20560369198
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.316736183525
===============================================================================
TimeReturn:
- 2005-12-31: 0.05517338686
- 2006-12-31: -0.195123836162
- 2007-12-31: -0.0441556438731
- 2008-12-31: -0.32426212721
- 2009-12-31: 0.153876836394
- 2010-12-31: 0.0167157437151
- 2011-12-31: -0.0202891373759
- 2012-12-31: 0.13289763017
- 2013-12-31: 0.0408192946307
- 2014-12-31: 0.0685527133815
===============================================================================
SharpeRatio:
- sharperatio: -0.154427699146
===============================================================================
SQN:
- sqn: -0.97846453428
- trades: 45
ORCL
$ ./macdsystem.py --plot --dataset orcl --cashalloc 0.50
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.69016747856
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 2.23991354467
===============================================================================
TimeReturn:
- 2005-12-31: -0.0597533502
- 2006-12-31: 0.176988400688
- 2007-12-31: 0.140268851352
- 2008-12-31: -0.0685193675128
- 2009-12-31: 0.195760054561
- 2010-12-31: 0.0956386594392
- 2011-12-31: 0.018709882089
- 2012-12-31: 0.100122407053
- 2013-12-31: -0.0375741196261
- 2014-12-31: 0.017570390931
===============================================================================
SharpeRatio:
- sharperatio: 0.518921692742
===============================================================================
SQN:
- sqn: 1.68844251174
- trades: 37
NVDA
$ ./macdsystem.py --plot --dataset nvda --cashalloc 0.50
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.128845648113
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.177604931253
===============================================================================
TimeReturn:
- 2005-12-31: 0.200593209479
- 2006-12-31: 0.219254906522
- 2007-12-31: 0.0407793562989
- 2008-12-31: -0.259567975403
- 2009-12-31: 0.380971100974
- 2010-12-31: -0.208860409742
- 2011-12-31: -0.189068154062
- 2012-12-31: -0.122095056225
- 2013-12-31: -0.0296495770432
- 2014-12-31: 0.0232050942344
===============================================================================
SharpeRatio:
- sharperatio: -0.0222780544339
===============================================================================
SQN:
- sqn: -0.190661428812
- trades: 38
Conclusions of Run 2
-
Increasing the percentage cash allocation in each operation from
20%
to50%
has increased the effect of the previous results-
The strategy on YHOO and NVDA has lost more than before
-
And the strategy on ORCL has won more than before, still not close to the over 220% of the asset.
-
Run 3: ATR Distance to 4.0
Still keeping the previous increase in cash allocation to 50%
. The idea is
to avoid getting too early out of the market.
YHOO
$ ./macdsystem.py --plot --dataset yhoo --cashalloc 0.50 --atrdist 4.0
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.01196310622
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.316736183525
===============================================================================
TimeReturn:
- 2005-12-31: 0.06476232676
- 2006-12-31: -0.220219327475
- 2007-12-31: -0.0525484648039
- 2008-12-31: -0.314772526784
- 2009-12-31: 0.179631995594
- 2010-12-31: 0.0579495723922
- 2011-12-31: -0.0248948026947
- 2012-12-31: 0.10922621981
- 2013-12-31: 0.406711050602
- 2014-12-31: -0.0113108751022
===============================================================================
SharpeRatio:
- sharperatio: 0.0495181271704
===============================================================================
SQN:
- sqn: -0.211652416441
- trades: 33
ORCL
$ ./macdsystem.py --plot --dataset orcl --cashalloc 0.50 --atrdist 4.0
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.21907748452
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 2.23991354467
===============================================================================
TimeReturn:
- 2005-12-31: -0.06660102614
- 2006-12-31: 0.169334910265
- 2007-12-31: 0.10620478758
- 2008-12-31: -0.167615289704
- 2009-12-31: 0.17616784045
- 2010-12-31: 0.0591200431984
- 2011-12-31: -0.100238247103
- 2012-12-31: 0.135096589522
- 2013-12-31: -0.0630483842399
- 2014-12-31: 0.0175914485158
===============================================================================
SharpeRatio:
- sharperatio: 0.144210776122
===============================================================================
SQN:
- sqn: 0.646519270815
- trades: 30
NVDA
$ ./macdsystem.py --plot --dataset nvda --cashalloc 0.50 --atrdist 4.0
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: 0.48840287049
===============================================================================
TimeReturn:
- 9999-12-31 23:59:59.999999: -0.177604931253
===============================================================================
TimeReturn:
- 2005-12-31: 0.246510998277
- 2006-12-31: 0.194958106054
- 2007-12-31: -0.123140650516
- 2008-12-31: -0.246174938322
- 2009-12-31: 0.33121185861
- 2010-12-31: -0.0442212647256
- 2011-12-31: 0.0368388717861
- 2012-12-31: -0.048473112136
- 2013-12-31: 0.10657587649
- 2014-12-31: 0.0883112536534
===============================================================================
SharpeRatio:
- sharperatio: 0.264551262551
===============================================================================
SQN:
- sqn: 0.564151633588
- trades: 29
Conclusions of Run 3
-
Chicken, Chicken, Winner Dinner!!
The strategy makes money on the 3 assets
-
YHOO:
1.19%
vs the31.67%
return of the asset itself -
ORCL:
21.90%
vs the223.99%
of the asset
In this case increasing the
ATRDist
parameter has decreased the previous returns of Run 2 which were at69.01%
- NVDA:
48.84%
vs-17.76%
of the asset.
The surprising thing here is that the SharpeRatio and SQN are telling that
-
Usage of the sample
$ ./macdsystem.py --help
usage: macdsystem.py [-h] (--data DATA | --dataset {yhoo,orcl,nvda})
[--fromdate FROMDATE] [--todate TODATE] [--cash CASH]
[--cashalloc CASHALLOC] [--commperc COMMPERC]
[--macd1 MACD1] [--macd2 MACD2] [--macdsig MACDSIG]
[--atrperiod ATRPERIOD] [--atrdist ATRDIST]
[--smaperiod SMAPERIOD] [--dirperiod DIRPERIOD]
[--riskfreerate RISKFREERATE] [--plot [kwargs]]
Sample for Tharp example with MACD
optional arguments:
-h, --help show this help message and exit
--data DATA Specific data to be read in (default: None)
--dataset {yhoo,orcl,nvda}
Choose one of the predefined data sets (default: None)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: None)
--cash CASH Cash to start with (default: 50000)
--cashalloc CASHALLOC
Perc (abs) of cash to allocate for ops (default: 0.2)
--commperc COMMPERC Perc (abs) commision in each operation. 0.001 -> 0.1%,
0.01 -> 1% (default: 0.0033)
--macd1 MACD1 MACD Period 1 value (default: 12)
--macd2 MACD2 MACD Period 2 value (default: 26)
--macdsig MACDSIG MACD Signal Period value (default: 9)
--atrperiod ATRPERIOD
ATR Period To Consider (default: 14)
--atrdist ATRDIST ATR Factor for stop price calculation (default: 3.0)
--smaperiod SMAPERIOD
Period for the moving average (default: 30)
--dirperiod DIRPERIOD
Period for SMA direction calculation (default: 10)
--riskfreerate RISKFREERATE
Risk free rate in Perc (abs) of the asset for the
Sharpe Ratio (default: 0.01)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)
And the code itself
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import random
import backtrader as bt
BTVERSION = tuple(int(x) for x in bt.__version__.split('.'))
class FixedPerc(bt.Sizer):
'''This sizer simply returns a fixed size for any operation
Params:
- ``perc`` (default: ``0.20``) Perc of cash to allocate for operation
'''
params = (
('perc', 0.20), # perc of cash to use for operation
)
def _getsizing(self, comminfo, cash, data, isbuy):
cashtouse = self.p.perc * cash
if BTVERSION > (1, 7, 1, 93):
size = comminfo.getsize(data.close[0], cashtouse)
else:
size = cashtouse // data.close[0]
return size
class TheStrategy(bt.Strategy):
'''
This strategy is loosely based on some of the examples from the Van
K. Tharp book: *Trade Your Way To Financial Freedom*. The logic:
- Enter the market if:
- The MACD.macd line crosses the MACD.signal line to the upside
- The Simple Moving Average has a negative direction in the last x
periods (actual value below value x periods ago)
- Set a stop price x times the ATR value away from the close
- If in the market:
- Check if the current close has gone below the stop price. If yes,
exit.
- If not, update the stop price if the new stop price would be higher
than the current
'''
params = (
# Standard MACD Parameters
('macd1', 12),
('macd2', 26),
('macdsig', 9),
('atrperiod', 14), # ATR Period (standard)
('atrdist', 3.0), # ATR distance for stop price
('smaperiod', 30), # SMA Period (pretty standard)
('dirperiod', 10), # Lookback period to consider SMA trend direction
)
def notify_order(self, order):
if order.status == order.Completed:
pass
if not order.alive():
self.order = None # indicate no order is pending
def __init__(self):
self.macd = bt.indicators.MACD(self.data,
period_me1=self.p.macd1,
period_me2=self.p.macd2,
period_signal=self.p.macdsig)
# Cross of macd.macd and macd.signal
self.mcross = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)
# To set the stop price
self.atr = bt.indicators.ATR(self.data, period=self.p.atrperiod)
# Control market trend
self.sma = bt.indicators.SMA(self.data, period=self.p.smaperiod)
self.smadir = self.sma - self.sma(-self.p.dirperiod)
def start(self):
self.order = None # sentinel to avoid operrations on pending order
def next(self):
if self.order:
return # pending order execution
if not self.position: # not in the market
if self.mcross[0] > 0.0 and self.smadir < 0.0:
self.order = self.buy()
pdist = self.atr[0] * self.p.atrdist
self.pstop = self.data.close[0] - pdist
else: # in the market
pclose = self.data.close[0]
pstop = self.pstop
if pclose < pstop:
self.close() # stop met - get out
else:
pdist = self.atr[0] * self.p.atrdist
# Update only if greater than
self.pstop = max(pstop, pclose - pdist)
DATASETS = {
'yhoo': '../../datas/yhoo-1996-2014.txt',
'orcl': '../../datas/orcl-1995-2014.txt',
'nvda': '../../datas/nvda-1999-2014.txt',
}
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)
comminfo = bt.commissions.CommInfo_Stocks_Perc(commission=args.commperc,
percabs=True)
cerebro.broker.addcommissioninfo(comminfo)
dkwargs = dict()
if args.fromdate is not None:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate is not None:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
# if dataset is None, args.data has been given
dataname = DATASETS.get(args.dataset, args.data)
data0 = bt.feeds.YahooFinanceCSVData(dataname=dataname, **dkwargs)
cerebro.adddata(data0)
cerebro.addstrategy(TheStrategy,
macd1=args.macd1, macd2=args.macd2,
macdsig=args.macdsig,
atrperiod=args.atrperiod,
atrdist=args.atrdist,
smaperiod=args.smaperiod,
dirperiod=args.dirperiod)
cerebro.addsizer(FixedPerc, perc=args.cashalloc)
# Add TimeReturn Analyzers for self and the benchmark data
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='alltime_roi',
timeframe=bt.TimeFrame.NoTimeFrame)
cerebro.addanalyzer(bt.analyzers.TimeReturn, data=data0, _name='benchmark',
timeframe=bt.TimeFrame.NoTimeFrame)
# Add TimeReturn Analyzers fot the annuyl returns
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
# Add a SharpeRatio
cerebro.addanalyzer(bt.analyzers.SharpeRatio, timeframe=bt.TimeFrame.Years,
riskfreerate=args.riskfreerate)
# Add SQN to qualify the trades
cerebro.addanalyzer(bt.analyzers.SQN)
cerebro.addobserver(bt.observers.DrawDown) # visualize the drawdown evol
results = cerebro.run()
st0 = results[0]
for alyzer in st0.analyzers:
alyzer.print()
if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for Tharp example with MACD')
group1 = parser.add_mutually_exclusive_group(required=True)
group1.add_argument('--data', required=False, default=None,
help='Specific data to be read in')
group1.add_argument('--dataset', required=False, action='store',
default=None, choices=DATASETS.keys(),
help='Choose one of the predefined data sets')
parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False,
default=None,
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--cashalloc', required=False, action='store',
type=float, default=0.20,
help=('Perc (abs) of cash to allocate for ops'))
parser.add_argument('--commperc', required=False, action='store',
type=float, default=0.0033,
help=('Perc (abs) commision in each operation. '
'0.001 -> 0.1%%, 0.01 -> 1%%'))
parser.add_argument('--macd1', required=False, action='store',
type=int, default=12,
help=('MACD Period 1 value'))
parser.add_argument('--macd2', required=False, action='store',
type=int, default=26,
help=('MACD Period 2 value'))
parser.add_argument('--macdsig', required=False, action='store',
type=int, default=9,
help=('MACD Signal Period value'))
parser.add_argument('--atrperiod', required=False, action='store',
type=int, default=14,
help=('ATR Period To Consider'))
parser.add_argument('--atrdist', required=False, action='store',
type=float, default=3.0,
help=('ATR Factor for stop price calculation'))
parser.add_argument('--smaperiod', required=False, action='store',
type=int, default=30,
help=('Period for the moving average'))
parser.add_argument('--dirperiod', required=False, action='store',
type=int, default=10,
help=('Period for SMA direction calculation'))
parser.add_argument('--riskfreerate', required=False, action='store',
type=float, default=0.01,
help=('Risk free rate in Perc (abs) of the asset for '
'the Sharpe Ratio'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()