Algorithmic trading is a field that’s generally quite daunting to beginners, forcing them to juggle learning advanced programming techniques and market mechanics. Throughout the process there’s usually not a lot of guidance, and even less coding examples. Our goal is to demystify this process and take you from beginner to quant with a hands-on lesson. We’ll program our own technical indicator and build a trading strategy on top of it. Along the way you’ll get a taste of the key tenants of quantitative trading methodology to be able to repeat the process all on your own.

We’ll be using the free and open-source Quantiacs Toolbox which supports both Python and MATLAB. The best part is having access to 15+ years of free historical market data for backtesting - we’ll definitely be taking advantage of that.

Figure 1 – Original Candlestick Apple stock daily chart (chart from TradingView)

Figure 2 – Heikin-Ashi Candlesticks on Apple stock daily chart (chart from TradingView)

Often times Heikin-Ashi is a purely visual aid, and as you can see from the charts it smooths out the bars to emphasize trends with streaks of increasing/decreasing bars. To build our system, we’ll have to dive into the math behind the indicator. For any indicator this is usually well documented and can be found with a quick Google search. Here’s what we get for Heikin-Ashi:

One thing you may notice immediately is that the Heikin-Ashi Open price is a result of the previous Heikin-Ashi values. So when you’re first starting to calculate Heikin-Ashi, how do you obtain “previous” values? Well the standard solution is to do this on the first run:Heikin-Ashi Candle CalculationsHA_Close = (Open + High + Low + Close) / 4 HA_Open = (previous HA_Open + previous HA_Close) / 2 HA_Low = minimum of Low, HA_Open, and HA_Close HA_High = maximum of High, HA_Open, and HA_Close

Heikin-Ashi Calculations on First RunHA_Close = (Open + High + Low + Close) / 4 HA_Open = (Open + Close) / 2 HA_Low = Low HA_High = High

1. Grab a default trading system template

2. Create a function to handle all the indicator math

3. Account for initial conditions

4. Create functions to handle trading logic and execution of trading positions

A good way to start is to copy the general framework from one of the sample trading systems. Let’s clear out all the contents and just keep the empty skeleton. Starting with settings, we can keep the default markets, but we can set lookback to 11 since that’s all that’s needed for Heikin-Ashi.

MATLAB

function out = HEIKIN(O, H, L, C, oldO, oldC) HA_Close = (O+H+L+C)/4; HA_Open = (oldO + oldC)/2; elements = [H; L; HA_Open; HA_Close]; HA_High = max(elements,[],1); HA_Low = min(elements,[],1); out = [HA_Close; HA_Open; HA_High; HA_Low]; endPython

def HEIKIN(O, H, L, C, oldO, oldC): HA_Close = (O + H + L + C)/4 HA_Open = (oldO + oldC)/2 elements = numpy.array([H, L, HA_Open, HA_Close]) HA_High = elements.max(0) HA_Low = elements.min(0) out = numpy.array([HA_Close, HA_Open, HA_High, HA_Low]) return out

Great, we’ve built a tidy function to do Heikin-Ashi calculations for us. Now let’s do a quick stop and make sure we fully understand what this function is doing. It takes input parameters that are all just a vector of 1 x Number of Markets (we’ll make sure that’s the case in initial conditions later), and averages them and finds maximums and minimums. For all of these operations we are working element-wise across the columns. For example, the max, is the max of each column in the elements matrix. Elements is just a stack of H, L etc. In each column, and each column represents a market, it looks for the highest or lowest values.

Markets |
|||

Lumber |
S&P 500 |
… | |

HA_Close |
21219 | 18000.63 | … |

HA_Open |
20996.25 | 18130 | … |

HA_High |
21538 | 18130 | … |

HA_Low |
20988 | 17805 | … |

Sample Heikin-Ashi Indicator Output (view as PDF)

Our trading logic also relies on knowing the previous Heikin-Ashi candles. In order for the trading logic to work on our initial run, we have to add a check to stop the HA_Close and HA_Open in from being set to the latest values within those initial conditions before reaching the trading logic. Lastly, we initialize an empty array

MATLAB

Python% Check if initial runif~exist('settings.HA_close','var')% Initial p vector, only need to define on first runsettings.lastP = zeros(1,numel(settings.markets));% Initial Heikin Valuessettings.HA_close = (OPEN(1,:) + HIGH(1,:) + LOW(1,:) + CLOSE(1,:))/4; settings.HA_open = (OPEN(1,:) + CLOSE(1,:))/2;% Run across lookback period, starting with 2nd rowfori=2:size(CLOSE,1) HAmatrix = HEIKIN(OPEN(i,:),HIGH(i,:),LOW(i,:),CLOSE(i,:),settings.HA_open,settings.HA_close);% To keep from running on latest value to use in trade logicifi < size(CLOSE,1) settings.HA_close = HAmatrix(1,:); settings.HA_open = HAmatrix(2,:);endend

defmyTradingSystem(DATE, OPEN, HIGH, LOW, CLOSE, settings):# Check if initial runif~hasattr(settings, 'HA_Close'): nMarkets = CLOSE.shape[1] nRows = CLOSE.shape[0]# Initial p vector, only need to define on first runsettings['lastP'] = numpy.zeros(nMarkets)# Initial Heikin Valuessettings['HA_Close'] = (OPEN[0,]+HIGH[0,]+LOW[0,]+CLOSE[0,])/4 settings['HA_Open'] = (OPEN[0,]+CLOSE[0,])/2# Run across lookback period, starting with 2nd rowforiinrange(1,nRows): HAmatrix = HEIKIN(OPEN[i,:],HIGH[i,:],LOW[i,:],CLOSE[i,:],settings['HA_Open'],settings['HA_Close'])# To keep from running on the latest value to use in trade logicifi < nRows-1: settings['HA_Close'] = HAmatrix[0,:] settings['HA_Open'] = HAmatrix[1,:]

So most of our trading system is now completely defined. We check to see if this is the trading system’s first run, if so we run Heikin-Ashi across the lookback period. If not, we just iterate across the latest date with the Heikin-Ashi function. We now have a custom market indicator, the Heikin-Ashi, at our disposal. It’s the perfect template for putting in our trading logic.

Let’s stop and assess this trading strategy and what our likelihood of profit is. Heikin-Ashi is a delayed indicator, where each Heikin-Ashi candle is behind the market’s candles. This makes it great for minimizing noise and showing trends. However, this trading strategy is focused on reversing whatever Heikin-Ashi indicates. What’s more, it plans to do this based on just 2 candles – assuming that is enough confirmation for a trend. The underlying assumptions are that markets are very trendy, trends can be confirmed with just 2 daily candles, and that Heikin-Ashi’s lag means that trends it identifies will reverse fast. Immediately we can see this system is unlikely to be very profitable. However, this won’t discourage us from building it and seeing what the actual results are.Go Long (buy) if all of these are met:Latest Heikin-Ashi candle is bearish Previous Heikin-Ashi candle was also bearish Latest Heikin-Ashi candle body is longer than the previous candle Latest Heikin-Ashi candle has no upper wickGo Short (sell) if all of these are met:Latest Heikin-Ashi candle is bullish Previous Heikin-Ashi candle was also bullish Latest Heikin-Ashi candle body is longer than the previous candle Latest Heikin-Ashi candle has no lower wickExit Conditions:Same as an opposing entry signal except no latest Heikin-Ashi candle body can be smaller

As for the code, we can create an array of Booleans for every entry/exit position. The reasoning behind making an array of them is to have each column represent one of the markets. Then the Boolean in a certain column represents whether a buying/selling condition has been met for that market. The input parameters for our function will just be the Heikin-Ashi matrix generated by our indicator function, and the previous HA values saved in the settings struct. Since the buy logic is almost the exact opposite of the sell logic, we really only need one set of Booleans. Similarly, since the exit logic is quite close to the entry logic, we can just reuse those Booleans there as well.

MATLAB

Pythonfunctionout = TRADES(HA, oldO, oldC)% Trading Logic - Naive Reversal from earnForex% -------- Entry -----------% Buying% the latest completed HA candle is bearish, HA_close < HA_openlong1 = HA(1,:) < HA(2,:);% body is longer than previous candle's body abslong2 = abs(HA(1,:) - HA(2,:)) > abs(oldC - oldO);% previous candle also bearishlong3 = oldC < oldO;% latest candle has no upper wick HA_open == HA_highlong4 = HA(2,:) == HA(3,:); long = long1 & long2 & long3 & long4;% Selling% latest candle is bullish% body is longer than previous candle's body% previous candle also bullish% latest candle has no lower wick HA_open == HA_lowshort4 = HA(2,:) == HA(4,:); short = ~long1 & long2 & ~long3 & short4;end

deftrades(HA, oldO, oldC):# Heikin Ashi Reversal Strategy# ------------- Entry ----------------# Buying# latest HA candle is bearish, HA_Close < HA_Openlong1 = HA[0,:] < HA[1,:]# current candle body is longer than previous candle bodylong2 = numpy.abs(HA[0,:] - HA[1,:]) > numpy.abs(oldC - oldO)# previous candle was bearishlong3 = oldC < oldO# latest candle has no upper wick HA_Open == HA_Highlong4 = HA[1,:] == HA[2,:] long = long1 & long2 & long3 & long4# Selling# latest candle bullish, previous candle bullish with smaller body# latest candle has no lower wick HA_Open == HA_Lowshort4 = HA[1,:] == HA[3,:] short = ~long1 & long2 & ~long3 & short4# ------------- Exit -----------------# Exiting Long Positions - same conditions as short except for candle bodylong_exit = ~long1 & ~long3 & short4# Exiting Short Positions - same conditions as long except for candle bodyshort_exit = long1 & long3 & long4 out = numpy.array([long, short, long_exit, short_exit])returnout

MATLAB

Pythonfunctionout = EXECUTE_P(L, S, L_e, S_e, oldP)% Splitting buy and sell from PPbought = oldP > 0; Psold = oldP < 0;% Close Long Positionsclosebuy = Pbought & L_e; oldP(closebuy) = 0;% Close Short Positionsclosesell = Psold & S_e; oldP(closesell) = 0;% Enter New Long PositionsoldP(L) = 1;% Enter New Short PositionsoldP(S) = -1; out = oldP;end

defexecuteP(L, S, L_e, S_e, oldP):# Split buy and sell from pPbought = oldP > 0 Psold = oldP < 0# Close Long PositionscloseBuy = Pbought & L_e oldP[closeBuy] = 0# Close Sort PositionscloseSell = Psold & S_e oldP[closeSell] = 0# Enter New Long PositionsoldP[L] = 1# Enter New Short PositionsoldP[S] = -1returnoldP

Python

python path/to/trading_system.pyAlternatively, you can also just hit run if you’re using the Anaconda IDE for Python.

MATLAB

runts(‘Trading System Name’)

Figure 3 - Heikin-Ashi trading strategy performance against 44 futures

As expected, the results aren’t great because of the underlying assumptions in this particular strategy: that markets are very trendy, trends can be confirmed with just 2 daily candles, and that Heikin-Ashi’s lag means that trends it identifies will reverse fast.

A common pitfall of quant strategy development is overfitting. A curve fit strategy is one that’s been optimized so well, it perfectly fits the past performance of the markets. The end result is that it will completely fail with future price action and market events. Overfitting will produce fantastic backtesting results from unrealistic and unprofitable trading strategies. Going back to our unsuccessful Heikin-Ashi strategy, we can demonstrate the dangers of overfitting. Since our trading system doesn’t have any modifiable input parameters, we can optimize the time period and the equities/futures it trades on. Essentially, we can arbitrarily fit Heikin-Ashi to a certain time period and certain markets to extract desirable performance numbers.

Here’s what that looks like:

Figure 4 - Curvefit one year returns from Heikin-Ashi on the symbols BAC, UNH, TWX

Wow a Sharpe Ratio of 4! All of a sudden our trading strategy goes from a complete failure on backtests to an amazing winner. Of course there are endless caveats. This “optimized” strategy would never work in the real world. The moment the start date of the backtest is moved out by a few years, all the perceived market edge evaporates. Arbitrarily hunting for good backtesting results is a dangerous practice and won’t produce truly profitable strategies. However, the backtest above can be approached as a learning lesson. For example, what sort of prevailing market conditions allowed the Heikin-Ashi to have a temporary edge? Were the markets trending together? Was there a consensus among market experts? This sort of investigation is a valid way to rationalize and approach trading strategy development.

One thing to point out is that optimization itself is perfectly fine, in fact there are entire books written on this topic alone. In general, optimization should be approached as a way to filter out some additional noise from an already profitable strategy.

A few pointers to help avoid overfitting a trading strategy:

· Use in sample and out of sample for backtesting your strategy. This merely means backtest over 2/3 of available historical data, do some optimization, then test your optimized strategy on the remaining 1/3 of your historical data.

· Avoid putting in knowledge of future market events. This means not stopping your strategy from trading during the 2008 recession, or using Apple as the stock for a long-only strategy since we already know it’s historically a great choice for buying and holding.

· Use walk forward analysis. This is a bit more complex but involves testing your strategy over a small period of time, optimizing it, then testing it on another equal period of time that’s out of sample. The intent is to mimic your own behavior in trading the strategy. There’s even a walk forward analysis toolbox for MATLAB.

This should provide a good framework for you to apply any sort of trading logic to Heikin-Ashi. A Google search will provide a multitude of alternative trading systems using Heikin-Ashi that will probably perform a bit better. Since we were able to create the code behind one of the more complex technical indicators, you should now feel comfortable enough to implement almost any technical indicator just by finding the math behind it and converting it into its own function.

If you have any feedback, follow ups, or inquiries feel free to contact me: denis@quantiacs.com