Intro to Algorithmic Trading with Heikin-Ashi

July, 06 2016

Sample Trading Performance
performance1
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

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.
finalHA1

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

Heikin-Ashi Candle Chart
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:

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

 

Let's Start Coding

Here’s what the process will look like:
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.

Custom Function

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.

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

Initial Conditions

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 array p for the first run because we’ll be saving each set of market positions to know when to exit.

MATLAB
% 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
Python
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,:]
 
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.

Trading Strategy

For this trading system we have the following entry conditions:
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
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.

MATLAB
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
Python
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

Now 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 what p is, I recommend taking a look at the rundown of the Quantiacs Toolbox.

MATLAB
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
Python
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

You 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.

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

MATLAB
runts(‘Trading System Name’)

Backtest Performance
performance2
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.

CURVE FITTING & OPTIMIZATION

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:

 

performance4.png
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