Gurrib (2020) is the first published research paper to analyze the predictive power of Ichimoku Clouds for the largest 10 stocks in the US energy sector. In this tutorial, we implement a similar strategy while reducing the effect of look-ahead bias integrated into the original study. Our findings show that while the strategy has an impressive 176 Sharpe ratio during the downfall of the 2020 oil price war, the strategy has worse performance than found by Gurrib (2020). We discover that throughout a 5 year backtest, the strategy fails to beat the benchmark of a popular energy sector ETF.


A vast amount of research studies have been published which document mixed results when utilizing technical analysis to forecast future prices of securities. The Ichimoku Cloud, one of the most widely-used technical indicators in Japan, was first publicized by Goichi Hosoda. In 1996, Hidenobu Sasaki reworked the framework to form the current charting analysis tool. This indicator is composed of 5 lines in a time series.

Gurrib (2020) finds that applying a simple trading strategy using the time series of the Ichimoku Cloud can increase the mean return of a basket containing the top energy stocks from 21.5% (buy-and-hold) to 194% over a 7 year period. The components of the Ichimoku Cloud that Gurrib (2020) utilizes in this strategy are the Chikou Span, Senkou Span A, and Senkou Span B. The Senkou Span lines form the top and bottom of the Ichimoku Cloud. The strategy that we trade off of these lines is defined as follows:

  • Long when the Chikou line crosses the top of the cloud from below.
  • Short when the Chikou line crosses the bottom of the cloud from above.

For better understanding, here is a visualization of the price of XOM, it's Ichimoku Cloud time series, and the resulting buy/sell signals.


Note: all of the plots throughout this tutorial are reproducible in the attached research notebook, along with some descriptive statistics for securities in the universe.


Universe Selection

Gurrib (2020) selects a universe of the 10 largest-weighed constituents of the S&P Composite 1500 Energy Index over the testing period. This inherently incorporates lookahead-bias into the study as the security weights are sourced over the period the trading simulation occurs. Furthermore, since the publication of Gurrib (2020), some of the securities have even been delisted. Thus, to eliminate lookahead-bias and avoid delistings, we implement a universe selection model that provides the trading system with the 10 largest companies in the energy sector as of the current date in the backtest. Since the largest companies change infrequently, we only refresh the universe on a monthly basis.

def SelectCoarse(self, algorithm, coarse):
    if algorithm.Time.month == self.month:
        return Universe.Unchanged
    return [ x.Symbol for x in coarse if x.HasFundamentalData ]

def SelectFine(self, algorithm, fine):
    self.month = algorithm.Time.month

    energy_stocks = [ f for f in fine if f.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Energy ]
    sorted_by_market_cap = sorted(energy_stocks, key=lambda x: x.MarketCap, reverse=True)
    return [ x.Symbol for x in sorted_by_market_cap[:self.fine_size] ]

Alpha Construction

The IchimokuCloudCrossOverAlphaModel emits an Insight object to hold a long position after the Chikou Span crosses over the top of the cloud for a given security. Additionally, the strategy is made symmetrical by entering short positions after the Chikou Span crosses below the bottom of the cloud. During construction of this Alpha model, we simply set up a dictionary to hold a SymbolData object for each Symbol in the universe.

class IchimokuCloudCrossOverAlphaModel(AlphaModel):
    symbol_data_by_symbol = {}

The SymbolData class constructor is shown below. We first set up two class variables, previous_location and direction. The former enables the algorithm to signal when the Chikou Span crosses over the boundaries of the Ichimoku Cloud. The latter is added to ensure we continue to emit daily insights in the proper direction. Inside the __init__ method is where we create the IchimokuKinkoHyo indicator and warm it up.

class SymbolData:
    previous_location = None
    direction = None

    def __init__(self, symbol, algorithm):
        # Create Ichimoku indicator
        self.ichimoku = IchimokuKinkoHyo()

        # Warm up indicator
        history = algorithm.History(symbol, self.ichimoku.WarmUpPeriod + 1, Resolution.Daily).loc[symbol]
        for idx, row in history.iterrows():
            if self.ichimoku.IsReady:
                self.previous_location = self.get_location()

            tradebar = TradeBar(idx, symbol, row.open, row.high, row.low, row.close, row.volume)

Determining the Indicator Location

We define the following helper method to return the location of the Chikou Span with respect to the cloud. The Alpha model utilizes this helper method to determine when the Chikou Span is exiting the Ichimoku Cloud.

def get_location(self):
    chikou = self.ichimoku.Chikou.Current.Value

    senkou_span_a = self.ichimoku.SenkouA.Current.Value
    senkou_span_b = self.ichimoku.SenkouB.Current.Value
    cloud_top = max(senkou_span_a, senkou_span_b)
    cloud_bottom = min(senkou_span_a, senkou_span_b)

    if chikou > cloud_top:
        return 1    # Above cloud
    if chikou < cloud_bottom:
        return -1   # Below cloud
    return 0        # Inside cloud

Alpha Update

As new TradeBars are provided to the Alpha model's Update method, we update the Ichimoku indicator of each symbol. We then emit insights for the symbols that have their Chikou Span breaking out of their respective Ichimoku Cloud in a new direction. To maintain positions while we wait for another crossover in the Ichimoku Cloud, we emit insights on a daily basis with 1-day duration.

def Update(self, algorithm, data):
    insights = []

    for symbol, symbol_data in self.symbol_data_by_symbol.items():
        if not data.ContainsKey(symbol) or data[symbol] is None:

        # Update indicator with the latest TradeBar

        # Determine insight direction
        current_location = symbol_data.get_location()
        if symbol_data.previous_location is not None: # Indicator is ready
            if symbol_data.previous_location != 1 and current_location == 1:
                symbol_data.direction = InsightDirection.Up
            if symbol_data.previous_location != -1 and current_location == -1:
                symbol_data.direction = InsightDirection.Down

        symbol_data.previous_location = current_location

        # Emit insight
        if symbol_data.direction:
            insight = Insight.Price(symbol, timedelta(days=1), symbol_data.direction)

    return insights

Portfolio Construction & Trade Execution

We utilize the EqualWeightingPortfolioConstructionModel and the ImmediateExecutionModel.

Relative Performance

To analyze the value of this trading strategy, we compare its performance to buying and holding a popular ETF tracking the energy sector. In this study, we use XLE, the Energy Select Sector SPDR® Fund, as the benchmark. We can see from the plot below how the portfolio would have performed just had we had just invested in the benchmark.


We now analyze the Sharpe ratio and annual standard deviation of returns for both the strategy and the benchmark. From the table below, we can see the results of the strategy and the benchmark over the entire backtest period, the Fall 2015 crisis, and the 2020 oil price war. The strategy has a lower Sharpe ratio than the benchmark across all of the time periods we tested, except for the crash during the 2020 oil price war, where it generated an impressive 176 Sharpe ratio. We can also see the strategy has a lower annual standard deviation accross all of the time frames, implying that the strategy has more consistent returns than the benchmark.

Period Name Start Date End Date Strategy Sharpe ASD
Backtest 1/1/2015 8/16/2020 Strategy -0.31 0.223
Benchmark -0.083 0.312
Fall 2015 8/10/2015 10/10/2015 Strategy -0.31 0.294
Benchmark 0.242 0.351
2020 Crash 2/19/2020 3/23/2020 Strategy 176.524 0.949
Benchmark -0.902 1.108
2020 Recovery 3/23/2020 6/8/2020 Strategy -1.556 0.447
Benchmark 46.068 0.703