I realise that I've never actually sat down and described my fully automated futures trading system in all it's detail; despite having runit for around 7.5 years now. That isn't because I want to keep it a secret - far from it! I've blogged or written books about all the various components of the system. Since I've made a fair few changes to my over the last year or so, it would seem to make sense to set down what the system looks like as of today.
You'll find reading this post much easier if you've already read my first book, "Systematic Trading". I'm expecting a christmas rush of book orders - I've bought all of my relatives copies, which I know they will be pleased to receive... for the 7th year in a row [Hope none of them are reading this, to avoid spoiling the 'surprise'!].
I'll be using snippets of code and configuration from my open source backtesting and trading engine, pysystemtrade. But you don't need to be a python or pystemtrade expert to follow along. Of course if you are, then you can find the full .yaml configuration of my system here. This code will run the system. Note that this won't work with the standard .csv supplied data files, since these don't include the 114 or so instruments in the config. But you can edit the instrument weights in the config to suit the markets you can actually trade.
In much of this post I'll also be linking to other blog posts I've written - no need to repeat myself ad infinitum.
I'll also be mentioning various data providers and brokers in this post. It's important to note that none of them are paying me to endorse them, and I have no connection with them apart from being a (reasonably) satisfied customer. In fact I'm paying them.... sadly.
Update: 5th January 2022, added many more instruments
- Dynamic optimisation using minimum tracking error (here and here)
- A systematic method for choosing instruments to trade or not
- Changing behaviour of rules when vol changes
- Handcrafting in it's automated variant
- Improving handcrafting correlation estimate adjustments and Sharpe Ratio estimate adjustments
- Estimating vol using a partially mean reverting method
- Skew as a trading rule
- Various other trading rules (no blog posts - read the book I'm currently writing and hope will come out next year!)
Which markets should we sample / trade
- Periodically survey the list of markets offered by my broker, interactivebrokers
- Create a list of markets I want to add data for. Gradually work my way through them (as of writing this post, my wish list has 64 instruments in it!)
- Regardless of whether I think I can actually trade them (see below), backfill their data using historic data from barchart.com
- Add them to my system, where I will start sampling their prices
- At this stage I won't be trading them until they're manually added to my system configuration
Because of the dynamic optimisation that my system uses, it's possible for me to calculate optimal positions for instruments that I can't / won't actually trade. And in any case, one might want to include such markets when backtesting - more data is always better!
I do however ignore the following markets when backtesting:
- Markets for which I have a duplicate (normally a different sized contract eg SP500 emini and micro; but could be a market traded in a different exchange) and where the duplicate is better. See this report.
- A couple of other markets where my data is just a bit unreliable (I might delete these instruments at some point unless things improve)
- The odd market which is so expensive I can't even just hold a constant position (i.e. the rolling costs alone are too much)
Then for trading purposes I ignore:
- Markets for which there are legal restrictions on me trading (for me right now, US equity sector futures; but could be eg Bitcoin for UK traders who aren't considered MiFID professionals)
- Markets which don't meet my requirements for costs and liqiuidity (again see here). This is why I sample markets for a while before trading them, to get an idea of their likely bid-ask spreads and hence trading costs
Which trading rules to use
- Momentum - EWMAC (See my first or third book)
- Breakout (blogpost)
- Relative (cross sectional within asset class) momentum (blogpost)
- Assettrend: asset class momentum (blogpost)
- Normalised momentum (blogpost)
Trading rule performance
Here are the crude Sharpe Ratios for each trading rule:
I say crude, because I've just taken the average performance weighting all instruments equally. In particular that means we might have a poor performance for a rule that trades quickly because I've used the performance from many expensive instruments which wouldn't actually have an allocation to that rule at all. I've highlighted these in italics. In bold are the rules that are genuine money losers:
- mean reversion in the wings
- mean reversion across assset classes
As discussed here for momentum like trading rules we see much worse performance when volatility rises. For these rules, I reduce the size of the forecast if volatility is higher than it's historic levels. The code that does that is here.
weights = dict(
trendy = 0.6,
other = 0.4)
weights = dict(
weights = dict(
Next all I have to do is exclude any rules which a particular instrument can't trade because the costs exceed my 'speed limit' of 0.01 SR units. So here for example are the weights for an expensive instrument, Eurodollar with zeros removed:
Forecast diversification multipliers will obviously be different for each instrument, and these are estimated using by standard method.
Position scaling: volatility calculation
Instrument performance and characteristics
'Bond & STIR': 0.19,
'Metals & Crypto': 0.13,
('min', '-13.32'), ('max', '11.26'), ('median', '0.09578'), ('mean', '0.1177'), ('std', '1.44'),
('ann_mean', '30.11'), ('ann_std', '23.04'),
('sharpe', '1.307'), ('sortino', '1.814'), ('avg_drawdown', '-9.205'),
('time_in_drawdown', '0.9126'), ('calmar', '0.7017'), ('avg_return_to_drawdown', '3.271'),
('avg_loss', '-0.9803'), ('avg_gain', '1.06'), ('gaintolossratio', '1.081'),
('profitfactor', '1.26'), ('hitrate', '0.5383'),
('t_stat', '9.508'), ('p_value', '2.257e-21')
[[('min', '-21.84'), ('max', '45.79'), ('median', '2.175'), ('mean', '2.55'),
('std', '7.768'), ('skew', '0.9368'), ('ann_mean', '30.55'), ('ann_std', '26.91'),
('sharpe', '1.135'), ('sortino', '2.177'), ('avg_drawdown', '-5.831'),
('time_in_drawdown', '0.6485'), ('calmar', '0.8371'),
('avg_return_to_drawdown', '5.239'), ('avg_loss', '-4.465'), ('avg_gain', '6.741'),
('gaintolossratio', '1.51'), ('profitfactor', '2.527'),
('hitrate', '0.626'), ('t_stat', '8.193'), ('p_value', '1.457e-15')]