Before my current circumstances, and before I was a photographer (see above), I used to make music for a living. Specifically, weird-ass techno/electronic music that many people found difficult or annoying. One of the ways I would find sonic inspiration was to use audio software to generate random sounds. I would record this stream of noisy squawkiness, sift through a lot of garbage, and occasionally find a useful gem. I would take these little bits of useful audio and turn them into gritty, weird dance music.
It’s possible to find dedicated software that dives deeply into finding non-obvious, non-linear connections between “features” of price data. For example, we can ask ourselves if today’s high of the price of oil is above its three-day moving average, and the S&P 500’s closing price is below yesterday’s open, will gold go up the next day? The danger of course is that you might – no, you WILL – hit upon a great-looking system that just so happens to look good for your test, but fails in real life. “Curve fitting” is a fact of life when developing trading systems, and you must take steps to reduce this.
Recently, I thought it would be fun to put together a rudimentary script that tests various combinations of the open, high, low and close of recent days in the past. It can’t compare to software dedicated to that purpose, of course. We can for example explore such questions as: if the open of two days ago is above the high of seven days ago, AND the close of three days ago is lower than today’s low, should I buy at the next open? Who knows! This little optimizer will tell me if there’s anything to it.
Now if you’re like me, you’re thinking what on earth can the comparison between prices 11 and 12 days ago have to do with current prices? Well…maybe something, maybe nothing. Let’s find out.
One thing I did realize quickly is that using a two-day moving average improved my testing. It seems to filter out some noise and improve the signal (or perhaps just allow me to curve-fit more tightly?). You could add this as another parameter to test, but you increase your processing time by adding another dimension.
I call my rudimentary push-button optimizer the “Comparinator”. If you’ve ever watched the cartoon Phineas and Ferb, the semi-evil Dr. Doofenshmirtz invents all sorts of evil devices with names ending in “-inator”. Now you can evilly compare OHLC data in the comfort of your own secret lair.
“Holy crap-inator, Matt, can you get to the point already? Show us a graph or something!”
OK, here’s a graph. Do you like it?
The gray line is SPY buy-and-hold. The orange is a system that the Comparinator developed. The out-of-sample performance is quite good. Also note that this system is long-only and seems to love the volatility of bear markets. It doesn’t like political shenanigans such as what happened in 2011, but it still does exceedingly well. Wait until you hear the trading details, which seem a little, well…random.
By the way, I developed this using a super-secret trading-system development platform (“SSTSDP”) for which I’m an alpha tester. The good news is that I’ve slapped together some AmiBroker code that does the same thing.
Here’s the pseudo code for the entry (using the SSTSDP syntax). This was created to trade SPY. When the below code evaluates to ‘true’, go long at the open of the next day.
ma(C,2)>ma(C,2) and ma(C,2)>ma(O,2)
In plain English: when the two-day moving average of the close of one day ago is greater than the two-day moving average of the close of today, AND the two-day moving average of the close 10 days ago is greater than the two-day moving average of the open 11 days ago, go long at the open of the next day.
Clear as mud, right? Let’s see if we can simplify it.
ma(C,2) > ma(C,2) ( C + C ) / 2 > ( C + C ) / 2 C + C > C + C C > C
The first half of that expression can be simplified to read: today’s close was below the close of two days ago. That’s much easier to figure out. Just don’t forget to include the other part. I attempted to rationalize why that part of the equation improves results, but I’ve decided just to point out that it seems to work.
The exit is simple: when the first part of the entry is no longer true, exit. I.e. when the close of today is NOT below the close of two days ago, exit at the open of the next day. Often this is a 24-hour hold, but the average is two days.
Here’s some AmiBroker code you can play with and come up with your own
overly curve-fit systems. Will they be valid in future? No idea. I do recommend that you find systems that show consistency throughout their equity curves, with lots of trades so your results are statistically significant.
A suggestion for using the code: run this to optimize for one comparison first (which will require 4096 permutations), using whatever characteristic you prefer. I chose the best sharpe ratio that had at least 200 trades over the period 2000-2010. Then make those values permanent. Next, un-comment the second section in the code to come up with your fine-tuning. Play around with the entries and exits: same day close, next day close, limit orders etc.
Remember also to only test on a portion of your data, and leave some as out-of-sample data to verify it works. Even then, don’t start trading actively right away. Let your systems stew for awhile, or trade with tiny sums.
This code is child’s play compared to a fully-compiled, dedicated application. Use it as a springboard for your own ideas.
#include_once "Formulas\Norgate Data\Norgate Data Functions.afl" oc = NorgateOriginalCloseTimeSeries(); //The Comparinator by Matt Haines //www.throwinggoodmoney.com SetTradeDelays(0,0,0,0); SetOption("initialequity",100000); SetOption ("MaxOpenPositions" , 1); SetOption ("allowsamebarexit",false); SetBacktestMode(backtestregular); SetOption("CommissionMode",2); SetOption("CommissionAmount",0); SetOption("MCUseEquityChanges",1); SetOption( "ExtraColumnsLocation", 1 ); Short=Cover=0; d1 = Optimize("d1",0,0,15,1); d2 = Optimize("d2",4,0,15,1); p1 = Optimize("p1",2,0,3,1); p2 = Optimize("p2",1,0,3,1); // at any one time, three of these segments below will return zero, and one of them will return // the price for the OHLC variable. val1= Ref(O,-d1)*(p1==0) + Ref(h,-d1)*(p1==1) + Ref(l,-d1)*(p1==2) + Ref(c,-d1)*(p1==3); val2= Ref(o,-d2)*(p2==0) + Ref(h,-d2)*(p2==1) + Ref(l,-d2)*(p2==2) + Ref(c,-d2)*(p2==3); //these variables are reset when second chunk is uncommented. //otherwise they result in a pass-thru for the Buy evaluation val3=1; val4=0; //uncomment after optimizing the first chunk /* d3 = Optimize("d3",2,0,15,1); d4 = Optimize("d4",1,0,15,1); p3 = Optimize("p3",1,0,3,1); p4 = Optimize("p4",0,0,3,1); val3= Ref(o,-d3)*(p3==0) + Ref(h,-d3)*(p3==1) + Ref(l,-d3)*(p3==2) + Ref(c,-d3)*(p3==3); val4= Ref(o,-d4)*(p4==0) + Ref(h,-d4)*(p4==1) + Ref(l,-d4)*(p4==2) + Ref(c,-d4)*(p4==3); */ BuyPrice=Open; Buy= Ref(MA(val1,2)>MA(val2,2) AND MA(val3,2)>MA(val4,2),-1); Sell=Ref(MA(val1,2)<=MA(val2,2),-1); SellPrice=open;