Intro to Algorithmic Trading with Heikin-Ashi



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.





Heikin-Ashi





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







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

Heikin-Ashi Candle Calculations HA_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 Run HA_Close = (Open + High + Low + Close) / 4 HA_Open = (Open + Close) / 2 HA_Low = Low HA_High = High

We’re going to build the famous Heikin-Ashi indicator. Here are the basics: Heikin-Ashi translates to “average bar” in Japanese, and provides traders with a way to isolate trends. The technique revolves around using two days of price data and combining them into one “averaged” candle. You can read up more on Heikin-Ashi here 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:

Let's Start Coding

Custom Function

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]; end

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

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

Initial Conditions

% Check if initial run if ~exist('settings.HA_close','var') % Initial p vector, only need to define on first run settings.lastP = zeros(1,numel(settings.markets)); % Initial Heikin Values settings.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 row for i=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 logic if i < size(CLOSE,1) settings.HA_close = HAmatrix(1,:); settings.HA_open = HAmatrix(2,:); end end

def myTradingSystem(DATE, OPEN, HIGH, LOW, CLOSE, settings): # Check if initial run if ~hasattr(settings, 'HA_Close'): nMarkets = CLOSE.shape[1] nRows = CLOSE.shape[0] # Initial p vector, only need to define on first run settings['lastP'] = numpy.zeros(nMarkets) # Initial Heikin Values settings['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 row for i in range(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 logic if i < nRows-1: settings['HA_Close'] = HAmatrix[0,:] settings['HA_Open'] = HAmatrix[1,:]

Trading Strategy

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 wick Go 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 wick Exit Conditions: Same as an opposing entry signal except no latest Heikin-Ashi candle body can be smaller

function out = TRADES(HA, oldO, oldC) % Trading Logic - Naive Reversal from earnForex % -------- Entry ----------- % Buying % the latest completed HA candle is bearish, HA_close < HA_open long1 = HA(1,:) < HA(2,:); % body is longer than previous candle's body abs long2 = abs(HA(1,:) - HA(2,:)) > abs(oldC - oldO); % previous candle also bearish long3 = oldC < oldO; % latest candle has no upper wick HA_open == HA_high long4 = 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_low short4 = HA(2,:) == HA(4,:); short = ~long1 & long2 & ~long3 & short4; end

def trades(HA, oldO, oldC): # Heikin Ashi Reversal Strategy # ------------- Entry ---------------- # Buying # latest HA candle is bearish, HA_Close < HA_Open long1 = HA[0,:] < HA[1,:] # current candle body is longer than previous candle body long2 = numpy.abs(HA[0,:] - HA[1,:]) > numpy.abs(oldC - oldO) # previous candle was bearish long3 = oldC < oldO # latest candle has no upper wick HA_Open == HA_High long4 = 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_Low short4 = HA[1,:] == HA[3,:] short = ~long1 & long2 & ~long3 & short4 # ------------- Exit ----------------- # Exiting Long Positions - same conditions as short except for candle body long_exit = ~long1 & ~long3 & short4 # Exiting Short Positions - same conditions as long except for candle body short_exit = long1 & long3 & long4 out = numpy.array([long, short, long_exit, short_exit]) return out

Executing Positions

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

def executeP(L, S, L_e, S_e, oldP): # Split buy and sell from p Pbought = oldP > 0 Psold = oldP < 0 # Close Long Positions closeBuy = Pbought & L_e oldP[closeBuy] = 0 # Close Sort Positions closeSell = Psold & S_e oldP[closeSell] = 0 # Enter New Long Positions oldP[L] = 1 # Enter New Short Positions oldP[S] = -1 return oldP

Backtesting

python path/to/trading_system.py

runts(‘Trading System Name’)





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

CURVE FITTING & OPTIMIZATION

Here’s what the process will look like:1. Grab a default trading system template2. Create a function to handle all the indicator math3. Account for initial conditions4. Create functions to handle trading logic and execution of trading positionsA 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.From here we need to start defining our custom indicator. Since the actual math behind Heikin-Ashi is pretty straightforward to implement in both MATLAB and Python, we can write a separate function to carry out that math. Although we don’t yet have a clear picture of how we’ll incorporate the function, we do know that Heikin-Ashi requires the following to be calculated: open, high, low, close, previous HA_Open, and previous HA_Close. Naturally these become the input parameters for our function. With these input parameters, both HA_Open and HA_Close (from the math above) become very easy to calculate. Since HA_Low is the lowest value of all elements, and HA_High is the highest, we can compute both of these from one set of elements (High, Low, HA_Open, and HA_Close). Taking the minimum or maximum of that set of elements gives us the lowest and highest values respectively. Finally, since we have just one row of data for each variable, our function can return or output a matrix where each variable, such as HA_Close, is its own row.MATLABPythonGreat, 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.The next step is to figure out how our function ties into the whole system, and plan for initial conditions. A unique aspect of Heikin-Ashi is that we’ll have to initialize it across the Lookback period once. A great way to test whether the trading system is being run for the first time, is to look for any custom defined fields in the settings struct. Now we have to prepare our Heikin-Ashi values so that we can easily pass them into our function. Having the first HA_Close and HA_Open defined separately based on the first day of available data allows us to easily pass in these values into our function as oldC and oldO. Our initial and latest HA_Close and HA_Open values will be saved as custom fields in the settings struct.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 arrayfor the first run because we’ll be saving each set of market positions to know when to exit.MATLABPythonSo 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.For this trading system we have the following entry conditions: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.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.MATLABPythonNow that we have our arrays of Booleans, we can execute our positions. In order to be able to close out long vs short positions, we’ll first separate our position array. From there we can simply execute long and short positions just by doing p(long) = 1 or p(short) = -1. This is why it’s advantageous to have separate long and short Boolean arrays. If you’re not quite sure of whatis, I recommend taking a look at the rundown of the Quantiacs Toolbox MATLABPythonYou can find the entire Heikin-Ashi trading system code on the Quantiacs GitHub . We can now run our trading system on the Quantiacs backtester to see how well it performs.PythonAlternatively, you can also just hit run if you’re using the Anaconda IDE for Python.MATLABAs 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.

If you have any feedback, follow ups, or inquiries feel free to contact us.

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.