avatarSofien Kaabar, CFA

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

6902

Abstract

div id="f2ac"><pre><span class="hljs-keyword">return</span> Data</pre></div><div id="a7ec"><pre>def deleter(Data, <span class="hljs-keyword">index</span>, <span class="hljs-keyword">times</span>):

<span class="hljs-keyword">for</span> i in range(<span class="hljs-number">1</span>, <span class="hljs-keyword">times</span> + <span class="hljs-number">1</span>):

    Data = np.delete(Data, <span class="hljs-keyword">index</span>, axis = <span class="hljs-number">1</span>)</pre></div><div id="8f1c"><pre><span class="hljs-keyword">return</span> Data

def jump(Data, jump):

Data = Data[jump:, ]

<span class="hljs-keyword">return</span> Data</pre></div><div id="4705"><pre><span class="hljs-keyword">def</span> <span class="hljs-title function_">indicator_plot_double</span>(<span class="hljs-params">Data, first_panel, second_panel, window = <span class="hljs-number">250</span></span>):</pre></div><div id="9358"><pre>fig, ax = plt.<span class="hljs-title function_ invoke__">subplots</span>(<span class="hljs-number">2</span>, figsize = (<span class="hljs-number">10</span>, <span class="hljs-number">5</span>))

ax[<span class="hljs-number">0</span>].<span class="hljs-title function_ invoke__">plot</span>(Data[-<span class="hljs-attr">window</span>:, first_panel], color = <span class="hljs-string">'black'</span>, linewidth = <span class="hljs-number">1</span>)
ax[<span class="hljs-number">0</span>].<span class="hljs-title function_ invoke__">grid</span>() 
 
ax[<span class="hljs-number">1</span>].<span class="hljs-title function_ invoke__">plot</span>(Data[-<span class="hljs-attr">window</span>:, second_panel], color = <span class="hljs-string">'brown'</span>, linewidth = <span class="hljs-number">1</span>)
ax[<span class="hljs-number">1</span>].<span class="hljs-title function_ invoke__">grid</span>()
ax[<span class="hljs-number">1</span>].<span class="hljs-title function_ invoke__">legend</span>()</pre></div><div id="764d"><pre><span class="hljs-attr">my_data</span> = pd.read_excel(<span class="hljs-string">'ISM_PMI.xlsx'</span>)</pre></div><div id="51f0"><pre><span class="hljs-attr">my_data</span> = np.array(my_data)</pre></div><p id="2c56">Now, we should have an array called my_data with both the S&amp;P500’s and the PMI’s historical data since 1970.</p><figure id="536e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*PAhW0BdPvwQ7OUuH.png"><figcaption><b>S&amp;P500 in the first panel and the ISM PMI in the second panel.</b></figcaption></figure><p id="83a8">Subjectively, we can place a barrier at 40 acting as a support for the economy and a resistance at 60 acting as a break for the economy. These will be our trading triggers for buy and sell signals. Surely, some hindsight bias exists here but the idea is not to formulate a past trading strategy, rather see if the barriers will continue working in the future. Following the intuition, we can have the following trading rules:</p><ul><li><b>Long (Buy) the stock market every time the ISM PMI reaches 40.</b></li><li><b>Short (Sell) the stock market every time the ISM PMI reaches 60.</b></li></ul><div id="41e4"><pre>def <span class="hljs-built_in">signal</span>(Data, ism_pmi, buy, sell):

Data = <span class="hljs-built_in">adder</span>(Data, <span class="hljs-number">2</span>)

for i in <span class="hljs-built_in">range</span>(<span class="hljs-built_in">len</span>(Data)):
    
    if Data[i, ism_pmi] &lt;= <span class="hljs-number">40</span> and Data[i - <span class="hljs-number">1</span>, buy] == <span class="hljs-number">0</span> and Data[i - <span class="hljs-number">2</span>, buy] == <span class="hljs-number">0</span> and Data[i - <span class="hljs-number">3</span>, buy] == <span class="hljs-number">0</span> and Data[i - <span class="hljs-number">4</span>, buy] == <span class="hljs-number">0</span> and Data[i - <span class="hljs-number">5</span>, buy] == <span class="hljs-number">0</span>:
        
        Data[i, buy] = <span class="hljs-number">1</span></pre></div><div id="c1be"><pre><span class="hljs-keyword">elif</span> Data[i, ism_pmi] &gt;= 60 and Data[i - 1, sell] == 0 and Data[i - 2, sell] == 0 and Data[i - 3, sell] == 0 and Data[i - 4, sell] == 0 and Data[i - 5, sell] == 0:
        
        Data[i, sell] = -1
        
<span class="hljs-built_in">return</span> Data</pre></div><figure id="2918"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*wIDLHdYtAyhv5XS3.png"><figcaption><b>Signal chart on the strategy.</b></figcaption></figure><div id="945b"><pre>def <span class="hljs-built_in">signal_chart_ohlc_color</span>(Data, what_bull, window = <span class="hljs-number">1000</span>):   
 
Plottable = Data[-window:, ]

fig, ax = plt.<span class="hljs-built_in">subplots</span>(figsize = (<span class="hljs-number">10</span>, <span class="hljs-number">5</span>))

plt.<span class="hljs-built_in">plot</span>(Data[-window:, <span class="hljs-number">1</span>], color = <span class="hljs-string">'black'</span>, linewidth = <span class="hljs-number">1</span>)    for i in <span class="hljs-built_in">range</span>(<span class="hljs-built_in">len</span>(Plottable)):
    
    if Plottable[i, <span class="hljs-number">2</span>] == <span class="hljs-number">1</span>:
        
        x = i
        y = Plottable[i, <span class="hljs-number">1</span>]
    
        ax.<span class="hljs-built_in">annotate</span>(<span class="hljs-string">' '</span>, xy = (x, y), 
                    arrowprops = <span class="hljs-built_in">dict</span>(width = <span class="hljs-number">9</span>, headlength = <span class="hljs-number">11</span>, headwidth = <span class="hljs-number">11</span>, facecolor = <span class="hljs-string">'green'</span>, color = <span class="hljs-string">'green'</span>))</pre></div><div id="18eb"><pre><span class="hljs-keyword">elif</span> Plottable[i, <span class="hljs-number">3</span>] == -<span class="hljs-number">1</span>:
        
        x = i
        y = Plottable[i, <span class="hljs-number">1</span>]
    
        ax.annotate(<span class="hljs-string">' '</span>, xy = (x, y), 
                    arrowprops = <span class="hljs-built_in">dict</span>(width = <span class="hljs-number">9</span>, headlength = -<span class="hljs-number">11</span>, headwidth = -<span class="hljs-number">11</span>, facecolor = <span class="hljs-string">'red'</span>, color = <span class="hljs-string">'red'</span>))

ax.set_facecolor((<span class="hljs-number">0.95</span>, <span class="hljs-number">0.95</span>, <span class="hljs-number">0.95</span>)) 
plt.legend() 
plt.grid()</pre></div><p id="9171">The code above gives us the signals shown in the chart. As a <b>buying-the-dips indicator</b>, the ISM PMI may require some tw

Options

eaking to improve the signals. Let us evaluate this using one metric for simplicity, the signal quality.</p><p id="01f0">The signal quality is a metric that resembles a fixed holding period strategy. It is simply the reaction of the market after a specified time period following the signal. Generally, when trading, we tend to use a variable period where we open the positions and close out when we get a signal on the other direction or when we get stopped out (either positively or negatively).</p><p id="4ed0">Sometimes, we close out at random time periods. Therefore, the signal quality is a very simple measure that assumes a fixed holding period and then checks the market level at that time point to compare it with the entry level. In other words, it measures market timing by checking the reaction of the market.</p><div id="d7bc"><pre><span class="hljs-comment"># Choosing a period</span> <span class="hljs-attr">period</span> = <span class="hljs-number">21</span></pre></div><div id="4fc6"><pre>def signal_quality(Data, closing, buy, sell, <span class="hljs-keyword">period</span>, <span class="hljs-keyword">where</span>):

Data <span class="hljs-operator">=</span> adder(Data, <span class="hljs-number">1</span>)

<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-keyword">range</span>(len(Data)):
    
    try:   
        
        if Data[i, buy] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">1</span>:
            
            Data[i <span class="hljs-operator">+</span> <span class="hljs-keyword">period</span>, <span class="hljs-keyword">where</span>] <span class="hljs-operator">=</span> Data[i <span class="hljs-operator">+</span> <span class="hljs-keyword">period</span>, closing] <span class="hljs-operator">-</span> Data[i, closing]
        
        if Data[i, sell] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">-1</span>:
            
            Data[i <span class="hljs-operator">+</span> <span class="hljs-keyword">period</span>, <span class="hljs-keyword">where</span>] <span class="hljs-operator">=</span> Data[i, closing] <span class="hljs-operator">-</span> Data[i <span class="hljs-operator">+</span> <span class="hljs-keyword">period</span>, closing]
            
    <span class="hljs-keyword">except</span> IndexError:
        
         pass
     
<span class="hljs-keyword">return</span> Data</pre></div><div id="5555"><pre><span class="hljs-comment"># Using a Window of signal Quality Check</span>

<span class="hljs-attr">my_data</span> = signal_quality(my_data, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, period, <span class="hljs-number">4</span>)</pre></div><div id="7af4"><pre><span class="hljs-attr">positives</span> = my_data[my_data[:, <span class="hljs-number">4</span>] > <span class="hljs-number">0</span>] <span class="hljs-attr">negatives</span> = my_data[my_data[:, <span class="hljs-number">4</span>] < <span class="hljs-number">0</span>]</pre></div><div id="674d"><pre><span class="hljs-comment"># Calculating Signal Quality</span> signal_quality = <span class="hljs-built_in">len</span>(positives) / (<span class="hljs-built_in">len</span>(negatives) + <span class="hljs-built_in">len</span>(positives))</pre></div><div id="bbc3"><pre><span class="hljs-built_in">print</span>(<span class="hljs-string">'Signal Quality = '</span>, <span class="hljs-built_in">round</span>(signal_quality * <span class="hljs-number">100</span>, <span class="hljs-number">2</span>), <span class="hljs-string">'%'</span>) <span class="hljs-comment"># Output: Signal Quality = 70.83%</span></pre></div><p id="bb5d">A signal quality of 70.83% means that on 100 trades, we tend to see in 71 of the cases a profitable result if we act on it, without taking into account transaction costs. We have had 24 signals based on the conditions provided. For an indicator quoted to be a leader, the results are disappointing especially compared to the other indicators we have seen previously. However, all is not lost. Many optimization techniques can be used to improve the results:</p><ul><li><b>Add a lag factor to the S&P500 so as to see if it delivers value with historical data.</b></li><li><b>Use another strategy based on the surpass or break of the 50 level.</b></li><li><b>Change the barriers from 40 and 60 to 35 and 65 and see if it helps.</b></li><li><b>See if a strategy based on exiting the barrier rather than entering the barrier is better.</b></li><li><b>Tweak the holding period to determine the optimal reaction window.</b></li><li><b>Change the conditions of entry and exit by including risk management and entry optimization such as closing out of a position before entering another.</b></li></ul><p id="3618">If you want to see how to create all sorts of algorithms yourself, feel free to check out Lumiwealth. From algorithmic trading to blockchain and machine learning, they have <i>hands-on</i> detailed courses that I highly recommend.</p><div id="fead" class="link-block"> <a href="https://www.lumiwealth.com/algorithmic-trading-landing-page/?utm_source=influence&amp;utm_medium=medium&amp;utm_campaign=sofien"> <div> <div> <h2>Learn Algorithmic Trading with Python Lumiwealth</h2> <div><h3>undefined</h3></div> <div><p>undefined</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*FbCz_ARtbOdywMlC)"></div> </div> </div> </a> </div><h1 id="e420">Summary</h1><p id="05a4">To sum up, what I am trying to do is to simply contribute to the world of objective technical analysis which is promoting more transparent techniques and strategies that need to be back-tested before being implemented. This way, technical analysis will get rid of the bad reputation of being subjective and scientifically unfounded.</p><p id="5d41">I recommend you always follow the the below steps whenever you come across a trading technique or strategy:</p><ul><li><i>Have a critical mindset and get rid of any emotions.</i></li><li><i>Back-test it using real life simulation and conditions.</i></li><li><i>If you find potential, try optimizing it and running a forward test.</i></li><li><i>Always include transaction costs and any slippage simulation in your tests.</i></li><li><i>Always include risk management and position sizing in your tests.</i></li></ul><p id="beb3">Finally, even after making sure of the above, stay careful and monitor the strategy because market dynamics may shift and make the strategy unprofitable.</p></article></body>

A Leading Fundamental Indicator for Equity Investing

Creating a Strategy on the S&P500 Using the ISM PMI in Python

Sentiment Analysis is a vast and promising field in data analytics and trading. It is a rapidly rising type of analysis that uses the current pulse and market feeling to detect what participants intend to do or what positions they are holding.

Imagine you are planning to go see a movie and you want to anticipate whether this movie will be good or not, therefore, you ask many of your friends — whom have already seen the movie — about their opinions. Assuming 75% said the movie was good, you will have a certain confidence level that you will like it because the sentiment around this movie was mostly good (ignoring tastes and preferences). The same analogy can be applied to the financial markets through many ways either quantitative or qualitative.

Sometimes, indicators will be classified as more than one type, meaning a technical indicator can also be a sentiment indicator (e.g. the On-Balance Volume). And the way we analyze can also be technical (e.g. drawing support and resistance lines) or quantitative (e.g. mean-reversion).

This article will discuss the Institute for Supply Management — Purchasing Manager’s Index and how is it used to analyze the direction of the S&P500. For this study, we will use the signal quality as a judge.

Knowledge must be accessible to everyone. This is why, from now on, a purchase of either one of my new books Contrarian Trading Strategies in PythonorTrend Following Strategies in Pythoncomes with free PDF copies of my first three books (Therefore, purchasing one of the new books gets you 4 books in total). The two new books listed above feature a lot of advanced indicators and strategies with a GitHub page. You can use the below link to purchase one of the two books (Please specify which one and make sure to include your e-mail in the note).

The ISM PMI

Sentiment Analysis is a type of predictive analytics that deal with alternative data other than the basic OHLC price structure. It is usually based on subjective polls and scores but can also be based on more quantitative measures such as expected hedges through market strength or weakness.

The Institute for Supply Management provides a monthly survey called the Purchasing Manager’s Index abbreviated to PMI, which is based on questions asked to 400 representatives of industrial companies about the current and future trend of their different activities. It is composed of 5 components that can also be analyzed individually:

  • New orders — 30% weight.
  • Production — 25% weight.
  • Employment — 20% weight.
  • Supplier Deliveries — 15% weight.
  • Inventories — 10% weight.

Typically, we interpret the index as 50 being the neutral state of the economy (not growing and neither shrinking). A reading above 50 indicates an expansion in manufacturing and a reading below 50 indicates shrinking in manufacturing. The PMI tends to peak before the whole economy with a lead of 6–12 months, which makes it an astonishing leading indicator (more than 0.70 correlation with the US GDP).

It also tends to lead the stock markets. Historically the values of the PMI fluctuated between 30 and 60, so, they serve as warning signals of possible reversal. The idea of the analysis is to verify this information by creating a strategy that generates directional recommendations on the S&P500 using the idea of mean-reversion.

Downloading the Data & Designing the Strategy

Let us download a sample of both historical data of the ISM PMI and the S&P500 since 1970. I have spared you the grunt work and compiled both into an excel file downloadable from my GitHub below. This way we can import the data directly into the Python interpreter and analyze it.

The first part is to download the ISM_PMI.xlsx data to your computer and then go into the Python interpreter and switch the source to where the excel file is located. Now, we have to import it and structure it.

import pandas                    as pd
import matplotlib.pyplot         as plt
import numpy                     as np
# Defining Primordial Functions
def adder(Data, times):
    
    for i in range(1, times + 1):
    
        new = np.zeros((len(Data), 1), dtype = float)
        Data = np.append(Data, new, axis = 1)
return Data
def deleter(Data, index, times):
    
    for i in range(1, times + 1):
    
        Data = np.delete(Data, index, axis = 1)
return Data
   
def jump(Data, jump):
    
    Data = Data[jump:, ]
    
    return Data
def indicator_plot_double(Data, first_panel, second_panel, window = 250):
fig, ax = plt.subplots(2, figsize = (10, 5))
    
    ax[0].plot(Data[-window:, first_panel], color = 'black', linewidth = 1)
    ax[0].grid() 
     
    ax[1].plot(Data[-window:, second_panel], color = 'brown', linewidth = 1)
    ax[1].grid()
    ax[1].legend()
my_data = pd.read_excel('ISM_PMI.xlsx')
my_data = np.array(my_data)

Now, we should have an array called my_data with both the S&P500’s and the PMI’s historical data since 1970.

S&P500 in the first panel and the ISM PMI in the second panel.

Subjectively, we can place a barrier at 40 acting as a support for the economy and a resistance at 60 acting as a break for the economy. These will be our trading triggers for buy and sell signals. Surely, some hindsight bias exists here but the idea is not to formulate a past trading strategy, rather see if the barriers will continue working in the future. Following the intuition, we can have the following trading rules:

  • Long (Buy) the stock market every time the ISM PMI reaches 40.
  • Short (Sell) the stock market every time the ISM PMI reaches 60.
def signal(Data, ism_pmi, buy, sell):
    
    Data = adder(Data, 2)
    
    for i in range(len(Data)):
        
        if Data[i, ism_pmi] <= 40 and Data[i - 1, buy] == 0 and Data[i - 2, buy] == 0 and Data[i - 3, buy] == 0 and Data[i - 4, buy] == 0 and Data[i - 5, buy] == 0:
            
            Data[i, buy] = 1
elif Data[i, ism_pmi] >= 60 and Data[i - 1, sell] == 0 and Data[i - 2, sell] == 0 and Data[i - 3, sell] == 0 and Data[i - 4, sell] == 0 and Data[i - 5, sell] == 0:
            
            Data[i, sell] = -1
            
    return Data
Signal chart on the strategy.
def signal_chart_ohlc_color(Data, what_bull, window = 1000):   
     
    Plottable = Data[-window:, ]
    
    fig, ax = plt.subplots(figsize = (10, 5))
    
    plt.plot(Data[-window:, 1], color = 'black', linewidth = 1)    for i in range(len(Plottable)):
        
        if Plottable[i, 2] == 1:
            
            x = i
            y = Plottable[i, 1]
        
            ax.annotate(' ', xy = (x, y), 
                        arrowprops = dict(width = 9, headlength = 11, headwidth = 11, facecolor = 'green', color = 'green'))
elif Plottable[i, 3] == -1:
            
            x = i
            y = Plottable[i, 1]
        
            ax.annotate(' ', xy = (x, y), 
                        arrowprops = dict(width = 9, headlength = -11, headwidth = -11, facecolor = 'red', color = 'red'))
    
    ax.set_facecolor((0.95, 0.95, 0.95)) 
    plt.legend() 
    plt.grid()

The code above gives us the signals shown in the chart. As a buying-the-dips indicator, the ISM PMI may require some tweaking to improve the signals. Let us evaluate this using one metric for simplicity, the signal quality.

The signal quality is a metric that resembles a fixed holding period strategy. It is simply the reaction of the market after a specified time period following the signal. Generally, when trading, we tend to use a variable period where we open the positions and close out when we get a signal on the other direction or when we get stopped out (either positively or negatively).

Sometimes, we close out at random time periods. Therefore, the signal quality is a very simple measure that assumes a fixed holding period and then checks the market level at that time point to compare it with the entry level. In other words, it measures market timing by checking the reaction of the market.

# Choosing a period
period = 21
def signal_quality(Data, closing, buy, sell, period, where):
    
    Data = adder(Data, 1)
    
    for i in range(len(Data)):
        
        try:   
            
            if Data[i, buy] == 1:
                
                Data[i + period, where] = Data[i + period, closing] - Data[i, closing]
            
            if Data[i, sell] == -1:
                
                Data[i + period, where] = Data[i, closing] - Data[i + period, closing]
                
        except IndexError:
            
             pass
         
    return Data
# Using a Window of signal Quality Check
my_data = signal_quality(my_data, 1, 2, 3, period, 4)
positives = my_data[my_data[:, 4] > 0]
negatives = my_data[my_data[:, 4] < 0]
# Calculating Signal Quality
signal_quality = len(positives) / (len(negatives) + len(positives))
print('Signal Quality = ', round(signal_quality * 100, 2), '%')
# Output: Signal Quality = 70.83%

A signal quality of 70.83% means that on 100 trades, we tend to see in 71 of the cases a profitable result if we act on it, without taking into account transaction costs. We have had 24 signals based on the conditions provided. For an indicator quoted to be a leader, the results are disappointing especially compared to the other indicators we have seen previously. However, all is not lost. Many optimization techniques can be used to improve the results:

  • Add a lag factor to the S&P500 so as to see if it delivers value with historical data.
  • Use another strategy based on the surpass or break of the 50 level.
  • Change the barriers from 40 and 60 to 35 and 65 and see if it helps.
  • See if a strategy based on exiting the barrier rather than entering the barrier is better.
  • Tweak the holding period to determine the optimal reaction window.
  • Change the conditions of entry and exit by including risk management and entry optimization such as closing out of a position before entering another.

If you want to see how to create all sorts of algorithms yourself, feel free to check out Lumiwealth. From algorithmic trading to blockchain and machine learning, they have hands-on detailed courses that I highly recommend.

Summary

To sum up, what I am trying to do is to simply contribute to the world of objective technical analysis which is promoting more transparent techniques and strategies that need to be back-tested before being implemented. This way, technical analysis will get rid of the bad reputation of being subjective and scientifically unfounded.

I recommend you always follow the the below steps whenever you come across a trading technique or strategy:

  • Have a critical mindset and get rid of any emotions.
  • Back-test it using real life simulation and conditions.
  • If you find potential, try optimizing it and running a forward test.
  • Always include transaction costs and any slippage simulation in your tests.
  • Always include risk management and position sizing in your tests.

Finally, even after making sure of the above, stay careful and monitor the strategy because market dynamics may shift and make the strategy unprofitable.

Trading
Data Science
Finance
Cryptocurrency
Investing
Recommended from ReadMedium