Home Trading Strategy Backtest Ichimoku Trading Strategy With Python – Part 2

Ichimoku Trading Strategy With Python – Part 2

by s666

This is part 2 of the Ichimoku Strategy creation and backtest – with part 1 having dealt with the calculation and creation of the individual Ichimoku elements (which can be found here), we now move onto creating the actual trading strategy logic and subsequent backtest.

The Ichimoku approach concerns itself with two major elements – firstly the signals and insights produced by the “cloud” structure, which is in trurn created by the interplay between the Senkou Span A and Senkou Span B (and sometimes its relation to the price), and secondly the interplay between the price, the Tenkan-sen and the Kijun-sen.

Let’s deal with the cloud element first:

There are two ways to identify the overall trend using the cloud. First, the trend is up when prices are above the cloud, down when prices are below the cloud and flat when prices are in the cloud. Second, the uptrend is strengthened when the Senkou Span A (green cloud line) is rising and above the Senkou Span B (red cloud line). This situation produces a green cloud (please note this is NOT currently the case on the chart I have produced as I am still working on getting that logic coded with Plotly – currently my cloud is just shaded all red, although the actual lines themselves are correctly coloured). Conversely, a downtrend is reinforced when the Leading Span A (green cloud line) is falling and below the Leading Span B (red cloud line). This situation produces a red cloud. Because the cloud is shifted forward 26 days, it also provides a glimpse of future support or resistance.

Now onto the second elements, the interplay between the price, the Tenkan-sen and the Kijun-sen:

The price, the Tenkan-sen and the Kijun-sen are used to identify faster and more frequent signals. It is important to remember that bullish signals are reinforced when prices are above the cloud and the cloud is green. Bearish signals are reinforced when prices are below the cloud and the cloud is red. In other words, bullish signals are preferred when the bigger trend is up (prices above green cloud), while bearish signals are preferred when the bigger trend is down (prices are below red cloud). This is the essence of trading in the direction of the bigger trend. Signals that are counter to the existing trend are deemed weaker, such as short-term bullish signals within a long-term downtrend or short-term bearish signals within a long-term uptrend.

When the Tenkan-sen crosses up through the Kijun-sen, that is considered a bullish signal and vice versa when the Tenkan-sen crosses down through the Kijun-sen that is considered a bearish signal.

With regards to the price and the Tenkan-sen, when prices cross up through the Tenkan-sen that is considered a bullish signal, and again vice versa when prices cross down through the Tenkan-sen that is considered a bearish signal.

Right so let us formalise our rules…

Buy when the following are true:

1) Prices are above the cloud 2) Senkou Span A is above Senkou Span B EITHER 3) the Tenkan-sen crosses up through the Kijun-sen OR 4) prices cross up through the Tenkan-sen

When either of these last two bullish criteria are met, the buy trade is opened at the start of the NEXT candle. The exit criteria is exit as soon as the Tenkan-sen crosses down through the Kijun-sen.

Sell when the following are true:

1) Prices are below the cloud 2) Senkou Span A is below Senkou Span B EITHER 3) the Tenkan-sen crosses down through the Kijun-sen OR 4) prices cross down through the Tenkan-sen

When either of these last two bearish criteria are met, the sell trade is opened at the start of the NEXT candle. The exit criteria is exit as soon as the Tenkan-sen crosses up through the Kijun-sen.

Let us now start the code – we import the necessary modules and retrieve our stock price data on which to run the strategy.

For completeness I have reproduced the relevant sections of code from the last “Part 1” article, allowing the creation of the Ichimoku constituents along with a Plotly chart of the resulting series.

import pandas as pd
import numpy as np
from pandas_datareader import data, wb
import datetime
import matplotlib.pyplot as plt

import plotly.plotly as py
import plotly.graph_objs as go
from plotly.offline import init_notebook_mode, plot, iplot

%matplotlib inline

init_notebook_mode(connected=True)


start = datetime.datetime(2000, 1, 1)
end = datetime.datetime(2019, 1, 27)
d=data.DataReader("F", 'yahoo', start, end)[['Open','High','Low','Close']]

# Tenkan-sen (Conversion Line): (9-period high + 9-period low)/2))
nine_period_high = d['High'].rolling(window= 9).max()
nine_period_low = d['Low'].rolling(window= 9).min()
d['tenkan_sen'] = (nine_period_high + nine_period_low) /2

# Kijun-sen (Base Line): (26-period high + 26-period low)/2))
period26_high = d['High'].rolling(window=26).max()
period26_low = d['Low'].rolling(window=26).min()
d['kijun_sen'] = (period26_high + period26_low) / 2

# Senkou Span A (Leading Span A): (Conversion Line + Base Line)/2))
d['senkou_span_a'] = ((d['tenkan_sen'] + d['kijun_sen']) / 2).shift(26)

# Senkou Span B (Leading Span B): (52-period high + 52-period low)/2))
period52_high = d['High'].rolling(window=52).max()
period52_low = d['Low'].rolling(window=52).min()
d['senkou_span_b'] = ((period52_high + period52_low) / 2).shift(52)

# The most current closing price plotted 26 time periods behind (optional)
d['chikou_span'] = d['Close'].shift(-26)

# Set colours for up and down candles
INCREASING_COLOR = '#17BECF'
DECREASING_COLOR = '#7F7F7F'

# create list to hold dictionary with data for our first series to plot
# (which is the candlestick element itself)
data1 = [ dict(
    type = 'candlestick',
    open = d.Open,
    high = d.High,
    low = d.Low,
    close = d.Close,
    x = d.index,
    yaxis = 'y2',
    name = 'F',
    increasing = dict( line = dict( color = INCREASING_COLOR ) ),
    decreasing = dict( line = dict( color = DECREASING_COLOR ) ),
) ]

# Create empty dictionary for later use to hold settings and layout options
layout=dict()

# create our main chart "Figure" object which consists of data to plot and layout settings
fig = dict( data=data1, layout=layout )

# Assign various seeting and choices - background colour, range selector etc
fig['layout']['plot_bgcolor'] = 'rgb(250, 250, 250)'
fig['layout']['xaxis'] = dict( rangeselector = dict( visible = True ) )
fig['layout']['yaxis'] = dict( domain = [0, 0.2], showticklabels = False )
fig['layout']['yaxis2'] = dict( domain = [0.2, 0.8] )
fig['layout']['legend'] = dict( orientation = 'h', y=0.9, x=0.3, yanchor='bottom' )
fig['layout']['margin'] = dict( t=40, b=40, r=40, l=40 )
fig['layout']['height'] = 900
fig['layout']['width'] = 1300


# Populate the "rangeselector" object with necessary settings
rangeselector=dict(
    visible = True,
    x = 0, y = 0.9,
    bgcolor = 'rgba(150, 200, 250, 0.4)',
    font = dict( size = 13 ),
    buttons=list([
        dict(count=1,
             label='reset',
             step='all'),
        dict(count=1,
             label='1yr',
             step='year',
             stepmode='backward'),
        dict(count=3,
            label='3 mo',
            step='month',
            stepmode='backward'),
        dict(count=1,
            label='1 mo',
            step='month',
            stepmode='backward'),
        dict(step='all')
    ]))
    

fig['layout']['xaxis']['rangeselector'] = rangeselector

# Append the Ichimoku elements to the plot
fig['data'].append( dict( x=d['tenkan_sen'].index, y=d['tenkan_sen'], type='scatter', mode='lines', 
                         line = dict( width = 1 ),
                         marker = dict( color = '#33BDFF' ),
                         yaxis = 'y2', name='tenkan_sen' ) )

fig['data'].append( dict( x=d['kijun_sen'].index, y=d['kijun_sen'], type='scatter', mode='lines', 
                         line = dict( width = 1 ),
                         marker = dict( color = '#F1F316' ),
                         yaxis = 'y2', name='kijun_sen' ) )

fig['data'].append( dict( x=d['senkou_span_a'].index, y=d['senkou_span_a'], type='scatter', mode='lines', 
                         line = dict( width = 1 ), 
                         marker = dict( color = '#228B22' ),
                         yaxis = 'y2', name='senkou_span_a' ) )

fig['data'].append( dict( x=d['senkou_span_b'].index, y=d['senkou_span_b'], type='scatter', mode='lines', 
                         line = dict( width = 1 ),fill='tonexty',
                         marker = dict( color = '#FF3342' ),
                         yaxis = 'y2', name='senkou_span_b' ) )

fig['data'].append( dict( x=d['chikou_span'].index, y=d['chikou_span'], type='scatter', mode='lines', 
                         line = dict( width = 1 ),
                         marker = dict( color = '#D105F5' ),
                         yaxis = 'y2', name='chikou_span' ) )


# Set colour list for candlesticks
colors = []

for i in range(len(d.Close)):
    if i != 0:
        if d.Close[i] > d.Close[i-1]:
            colors.append(INCREASING_COLOR)
        else:
            colors.append(DECREASING_COLOR)
    else:
        colors.append(DECREASING_COLOR)
        
iplot( fig, filename = 'candlestick-ichimoku' )

Great it looks like we have correctly generated the base DataFrame with the Ichimoku constituents. Now time to start the trading logic.First we drop any “Na” values from our DataFrame and then set the “regime marker” that will signify whether the prices are currently above, below or within the cloud structure. Then we use the same idea to set a column signifying whether the Senkou Span A is above or below the Senkou Span B.

d.dropna(inplace=True)

d['above_cloud'] = 0
d['above_cloud'] = np.where((d['Low'] > d['senkou_span_a'])  & (d['Low'] > d['senkou_span_b'] ), 1, d['above_cloud'])
d['above_cloud'] = np.where((d['High'] < d['senkou_span_a']) & (d['High'] < d['senkou_span_b']), -1, d['above_cloud'])

d['A_above_B'] = np.where((d['senkou_span_a'] > d['senkou_span_b']), 1, -1)

The we proceed to create two new columns, firstly one identifying the rows of the data which correspond with an occurrence of a cross over of the Tenkan-sen and the Kiju-sen (1 is entered as a value if it is a cross up of the Tenkan-sen through the Kijun-sen and -1 if its a cross downwards). The second identifies the rows of data that correspond to the “Open” price with the Tenkan-sn line (similarly to above, 1 is entered as a value if it is a cross up of the price through the Tenkan-sen and -1 if its a cross downwards).

d['tenkan_kiju_cross'] = np.NaN
d['tenkan_kiju_cross'] = np.where((d['tenkan_sen'].shift(1) <= d['kijun_sen'].shift(1)) & (d['tenkan_sen'] > d['kijun_sen']), 1, d['tenkan_kiju_cross'])
d['tenkan_kiju_cross'] = np.where((d['tenkan_sen'].shift(1) >= d['kijun_sen'].shift(1)) & (d['tenkan_sen'] < d['kijun_sen']), -1, d['tenkan_kiju_cross'])

d['price_tenkan_cross'] = np.NaN
d['price_tenkan_cross'] = np.where((d['Open'].shift(1) <= d['tenkan_sen'].shift(1)) & (d['Open'] > d['tenkan_sen']), 1, d['price_tenkan_cross'])
d['price_tenkan_cross'] = np.where((d['Open'].shift(1) >= d['tenkan_sen'].shift(1)) & (d['Open'] < d['tenkan_sen']), -1, d['price_tenkan_cross'])

The code below deals with the creation of our actual buy and sell entry signals.

Firstly we create a ‘buy’ columns and fill it with “np.NaN” values to act as a placeholder. For the buys, the value of a row in the ‘buy’ column is set to 1 if the first two criteria and met (Prices are above the cloud AND Senkou Span A is above Senkou Span B), and EITHER of the final 2 criteria are met (the Tenkan-sen crosses up through the Kijun-sen OR 4) prices cross up through the Tenkan-sen). For any rows where these criteria are not met, the value on the ‘buy’ column remains as a np.NaN value.

Then we identify the rows in the ‘buy’column that correspond with a positive instance of our closing criteria (i.e. a crossing down of the Tenkan-sen through the Kijun-sen) We fill these rows of the ‘buy’ column with a 0.

We then use the “ffill” method to “forward fill” our ‘buy’ column values to fill the gaps currently holding np.NaN values. so now the ‘buy’ column will hold a value of 1 while the strategy has signalled we should be in a long position, and a 0 while the strategy signals we should not be in a long position.

The same logic is then repeated in order to fill our strategy ‘sell’ columns with the signals of course reversed to correctly represent our strategy logic on the “short” side.

Finally the “buy” and “sell” column are added to create a new column holding our net strategy “position”.

d['buy'] = np.NaN
d['buy'] = np.where((d['above_cloud'].shift(1) == 1) & (d['A_above_B'].shift(1) == 1) & ((d['tenkan_kiju_cross'].shift(1) == 1) | (d['price_tenkan_cross'].shift(1) == 1)), 1, d['buy'])
d['buy'] = np.where(d['tenkan_kiju_cross'].shift(1) == -1, 0, d['buy'])
d['buy'].ffill(inplace=True)


d['sell'] = np.NaN
d['sell'] = np.where((d['above_cloud'].shift(1) == -1) & (d['A_above_B'].shift(1) == -1) & ((d['tenkan_kiju_cross'].shift(1) == -1) | (d['price_tenkan_cross'].shift(1) == -1)), -1, d['sell'])
d['sell'] = np.where(d['tenkan_kiju_cross'].shift(1) == 1, 0, d['sell'])
d['sell'].ffill(inplace=True)

d['position'] = d['buy'] + d['sell']

A “stock_returns” column is then created holding the log returns of the stock we are running the backtest on, which are then multiplied by the strategy “position” to correctly record the strategy returns depending on whether we are currently long or short.

Those two returns series are then cumulatively summed and plotted together for comparison using the built in Pandas plotting functionality.

d['stock_returns'] = np.log(d['Open']) - np.log(d['Open'].shift(1))
d['strategy_returns'] = d['stock_returns'] * d['position']

d[['stock_returns','strategy_returns']].cumsum().plot(figsize=(15,8))

In order to allow us to easily run the strategy over different stocks/assets, I have moved the code into a function which can be called with differing ticker symbols and date ranges passed as inputs.

def ichimoku(ticker, start, end):

    d=data.DataReader(ticker, 'yahoo', start, end)[['Open','High','Low','Close']]

    # Tenkan-sen (Conversion Line): (9-period high + 9-period low)/2))
    nine_period_high = d['High'].rolling(window= 9).max()
    nine_period_low = d['Low'].rolling(window= 9).min()
    d['tenkan_sen'] = (nine_period_high + nine_period_low) /2

    # Kijun-sen (Base Line): (26-period high + 26-period low)/2))
    period26_high = d['High'].rolling(window=26).max()
    period26_low = d['Low'].rolling(window=26).min()
    d['kijun_sen'] = (period26_high + period26_low) / 2

    # Senkou Span A (Leading Span A): (Conversion Line + Base Line)/2))
    d['senkou_span_a'] = ((d['tenkan_sen'] + d['kijun_sen']) / 2).shift(26)

    # Senkou Span B (Leading Span B): (52-period high + 52-period low)/2))
    period52_high = d['High'].rolling(window=52).max()
    period52_low = d['Low'].rolling(window=52).min()
    d['senkou_span_b'] = ((period52_high + period52_low) / 2).shift(52)

    # The most current closing price plotted 26 time periods behind (optional)
    d['chikou_span'] = d['Close'].shift(-26)

    d.dropna(inplace=True)

    d['above_cloud'] = 0
    d['above_cloud'] = np.where((d['Low'] > d['senkou_span_a'])  & (d['Low'] > d['senkou_span_b'] ), 1, d['above_cloud'])
    d['above_cloud'] = np.where((d['High'] < d['senkou_span_a']) & (d['High'] < d['senkou_span_b']), -1, d['above_cloud'])

    d['A_above_B'] = np.where((d['senkou_span_a'] > d['senkou_span_b']), 1, -1)

    d['tenkan_kiju_cross'] = np.NaN
    d['tenkan_kiju_cross'] = np.where((d['tenkan_sen'].shift(1) <= d['kijun_sen'].shift(1)) & (d['tenkan_sen'] > d['kijun_sen']), 1, d['tenkan_kiju_cross'])
    d['tenkan_kiju_cross'] = np.where((d['tenkan_sen'].shift(1) >= d['kijun_sen'].shift(1)) & (d['tenkan_sen'] < d['kijun_sen']), -1, d['tenkan_kiju_cross'])

    d['price_tenkan_cross'] = np.NaN
    d['price_tenkan_cross'] = np.where((d['Open'].shift(1) <= d['tenkan_sen'].shift(1)) & (d['Open'] > d['tenkan_sen']), 1, d['price_tenkan_cross'])
    d['price_tenkan_cross'] = np.where((d['Open'].shift(1) >= d['tenkan_sen'].shift(1)) & (d['Open'] < d['tenkan_sen']), -1, d['price_tenkan_cross'])

    d['buy'] = np.NaN
    d['buy'] = np.where((d['above_cloud'].shift(1) == 1) & (d['A_above_B'].shift(1) == 1) & ((d['tenkan_kiju_cross'].shift(1) == 1) | (d['price_tenkan_cross'].shift(1) == 1)), 1, d['buy'])
    d['buy'] = np.where(d['tenkan_kiju_cross'].shift(1) == -1, 0, d['buy'])
    d['buy'].ffill(inplace=True)


    d['sell'] = np.NaN
    d['sell'] = np.where((d['above_cloud'].shift(1) == -1) & (d['A_above_B'].shift(1) == -1) & ((d['tenkan_kiju_cross'].shift(1) == -1) | (d['price_tenkan_cross'].shift(1) == -1)), -1, d['sell'])
    d['sell'] = np.where(d['tenkan_kiju_cross'].shift(1) == 1, 0, d['sell'])
    d['sell'].ffill(inplace=True)

    d['position'] = d['buy'] + d['sell']

    d['stock_returns'] = np.log(d['Open']) - np.log(d['Open'].shift(1))
    d['strategy_returns'] = d['stock_returns'] * d['position']

    d[['stock_returns','strategy_returns']].cumsum().plot(figsize=(15,8))

Here is an example run for Netflix over the 2000 to 2019 time period.

ticker = 'NFLX'
start = datetime.datetime(2000, 1, 1)
end = datetime.datetime(2019, 1, 1)

ichimoku(ticker, start, end)

I’ll leave it there for now as we have the basic functionality and output that we were aiming to achieve. The strategy could of course be adapted and improved no doubt by tweaking the rules and logic – perhaps setting a stop loss element or tweaking the entry/exit criteria, but I shall leave that up to you lot if you are so inclined to try.

Any questions or comments etc, please do leave them below and I will do my best to reply.

Until next time…

You may also like

9 comments

da June 28, 2019 - 11:15 pm

I believe that you did not import numpy

Reply
s666 June 28, 2019 - 11:52 pm

Hi DA, thanks for pointing that out! I will fix it now 😉

Reply
Kaijser July 13, 2019 - 10:42 pm

Thanks for this blog. I have been getting into learning Python in order to automate my Ichimoku based trading strategy and found your website by surprise. In particular, i’d like to recreate this project: https://github.com/kumotrader/ichimoku-crypto, and turn it into a fully functioning bot for Telegram, which plots ichimoku charts on demand. It hasn’t worked out for me yet, and given that I have only recently started learning Python it might take a while for me to succeed in recreating this.

According to the picture posted on the GitHub page, the guy has managed to succesfully create Ichimoku clouds, so it might help you.

The settings that were used for the conversion line, baseline etc. are known as ‘doubled settings’ and are often used for cryptocurrency trading (20, 60, 120, 30)

Reply
s666 October 19, 2019 - 2:37 am

Hi Kaijser, sorry I must have completely missed this comment when it was made… Thank you for the github link and hope your ichimoku project is going well!!

Reply
Marcelo October 19, 2019 - 2:19 am

Hello, congrats for the post. Thanks for sharing your work.
I believe the shift for the senkou span b is 26, and not 52.

Reply
s666 October 19, 2019 - 2:35 am

Hi Marcelo, thanks for the comment and for pointing that out. I had actually been notified previously about the calculation being made using the last 52 periods and then projecting/shifting it forward 26 periods as you say, rather than also shifting it by 52 periods. I had made the correction to part 1 of the article but I obviously forgot this part!! I shall make the changes as soon as I get a moment. Cheers

Reply
Chris November 13, 2019 - 6:50 am

Thank you for this post! I am quite new in Python and just started to code Ichimoku-Signals. Do you have any idea how to implement multi timeframe indicators or do you know any helpful tutorial?! For example let’s assume that we calculate the Ichimoku cloud on H4-candlesticks and we want to receive a signal on the m30 data if the open price of the 30min is below and the close above the Span B?

Reply
s666 November 16, 2019 - 7:00 pm

Hi Chris, I guess one way would be to work with data that is of the granularity at which you wat to receive signals etc. – so in your example you would be working with 30 minute data, and then when you are writing the code that deals with the creation of the actual Ichimoku constituent series/columns/data – just muliply the rolling window periods so that they work on the same overall time window that you would like them to. So for instance, if you are basing your Ichimoku rolling window periods on the 4 Hour candlesticks – just multiply the window lengths by (4 hours / 30 minutes) 8 if you are uising 30 minutes data, and you should in essence be able to replicate what you are looking for!

Reply
Chris November 13, 2019 - 6:53 am

I have missed to say, that the previous question relates to backtesting… It‘s not a problem with live Date. But I want to see the signals of the past.

Reply

Leave a Reply

%d bloggers like this: