Learn how you can become a Python programmer in just 12 weeks.

    We respect your privacy. Unsubscribe at anytime.

    Master Dow Theory with Python Pandas

    What will we cover in this tutorial?

    Dow theory was proposed by Charles H. Dow and is not an exact science. It is more how to identify trends in the market. In this tutorial we investigate the approach by testing it on data. Notice, that there are various ways to interpret it and often it is done by visual approximations, while we in this tutorial will make some rough assumptions to see if it beats the buy-and-hold approach of a stock.

    First we will make our assumption on how to implement the Dow theory approach to make buy and sell indicators, which we will use as buy and sell markers in the market.

    Step 1: Understand the Dow theory to make buy and sell indicators

    The essence of Dow theory is that there are 3 types of trend in the market. The primary trend is a year or more long trend, like a bull market. Then on a secondary trend, the market can move in opposite direction for 3 weeks to 3 months. This can result in a pullback, that can seem like a bear market within the bull market. Finally, there are micro trends (less than 3 weeks) which can be considered as noise.

    According to Dow theory each market has 3 phases. Our objective as an investor is to identify when a bear market turns into bull market.

    Some visual example to understand the above will help a bit. A general bull market with primary and secondary trends could look like this.

    Primary bull market trend with secondary bear market trends.

    Where you should notice that the temporary lows are all increasing along the way.

    A similar picture for a bear market could be.

    Primary bear market trend with secondary bull market trends.

    Here you should notice how the secondary bull markets peaks are also in a decreasing trend.

    Step 2: Identify when a primary market trend changes

    The key here is to identify when a primary stock trend goes from bull to bear or opposite.

    Please also notice that Dow theory talks about the market and we here are looking at a stock. Hence, we have an assumption that the market and the stock have a strong enough correlation to use the same theory.

    From a primary bear to a primary bull market could look like as follows.

    From bear to bull market

    We have added some markers in the diagram.

    • LL : Low-Low – meaning that the low is lower than previous low.
    • LH : Low-High – meaning that the high is lower than previous high.
    • HH : High-High – meaning that the high is higher than previous high.
    • HL : High-Low – meaning that the low is higher than previous low.

    As you see, the bear market consists of consecutive LL and LH, while a bull market consists of consecutive HH and LH. The market changes from bear to bull when we confidently can say that we will get a HH, which we can do when we cross from the last LL over the last LH (before we reach HH).

    Hence, a buy signal can be set when we reach a stock price above last LH.

    Similar we can investigate the when a primary trends goes from bull to hear market.

    From bull to a bear trend.

    Where we have the same types of markers.

    We see that the trend changes from bull to bear when we go from HL to LL. Hence, a sell indicator is when we are sure we reach a LL (that is before it is a LL).

    Again, this is not an exact science and is just a way to interpret it. We will try it out on real stock data to see how it performs.

    Step 3: Get some data and calculate points of lows and highs

    We will use Pandas-datareader to get the time series data from Yahoo! Finance.

    import pandas_datareader as pdr
    import datetime as dt
    
    ticker = pdr.get_data_yahoo("TWTR", dt.datetime(2020,1,1), dt.datetime.now())
    print(ticker)
    

    Resulting in a time series for Twitter, which has the ticker TWTR. You can find other tickers for other companies by using the Yahoo! Finance ticker lookup.

                     High        Low       Open      Close    Volume  Adj Close
    Date                                                                       
    2020-01-02  32.500000  31.959999  32.310001  32.299999  10721100  32.299999
    2020-01-03  32.099998  31.260000  31.709999  31.520000  14429500  31.520000
    2020-01-06  31.709999  31.160000  31.230000  31.639999  12582500  31.639999
    2020-01-07  32.700001  31.719999  31.799999  32.540001  13712900  32.540001
    2020-01-08  33.400002  32.349998  32.349998  33.049999  14632400  33.049999
    ...               ...        ...        ...        ...       ...        ...
    2020-08-12  38.000000  36.820000  37.500000  37.439999  11013300  37.439999
    2020-08-13  38.270000  37.369999  37.430000  37.820000  13259400  37.820000
    2020-08-14  37.959999  37.279999  37.740002  37.900002  10377300  37.900002
    2020-08-17  38.090000  37.270000  37.950001  37.970001  10188500  37.970001
    2020-08-18  38.459999  37.740002  38.279999  38.009998   8548300  38.009998
    

    First thing we need to get is to find the low and highs. First challenge here is that the stock price is going up and down during the day. To simplify our investigation we will only use the Close price.

    Taking that decision might limit and not give correct results, but it surely simplifies our work.

    Next up, we need to identify highs and lows. This can be done to see when a daily difference goes from positive to negative.

    import pandas_datareader as pdr
    import datetime as dt
    
    ticker = pdr.get_data_yahoo("TWTR", dt.datetime(2020,1,1), dt.datetime.now())
    ticker['delta'] = ticker['Close'].diff()
    growth = ticker['delta'] > 0
    ticker['markers'] = growth.diff().shift(-1)
    print(ticker)
    

    Please notice the shit(-1) as it moves the indicator on the day of the change.

    2020-08-05  37.340000  36.410000  36.560001  36.790001   10052100  36.790001  0.440002   False
    2020-08-06  37.810001  36.490002  36.849998  37.689999   10478900  37.689999  0.899998    True
    2020-08-07  38.029999  36.730000  37.419998  37.139999   11335100  37.139999 -0.549999    True
    2020-08-10  39.169998  37.310001  38.360001  37.439999   29298400  37.439999  0.299999    True
    2020-08-11  39.000000  36.709999  37.590000  37.279999   20486000  37.279999 -0.160000    True
    2020-08-12  38.000000  36.820000  37.500000  37.439999   11013300  37.439999  0.160000   False
    2020-08-13  38.270000  37.369999  37.430000  37.820000   13259400  37.820000  0.380001   False
    2020-08-14  37.959999  37.279999  37.740002  37.900002   10377300  37.900002  0.080002   False
    2020-08-17  38.090000  37.270000  37.950001  37.970001   10188500  37.970001  0.070000   False
    2020-08-18  38.459999  37.740002  38.279999  38.009998    8548300  38.009998  0.039997     NaN
    

    Where we have output above. The True values are when we reach Highs or Lows.

    Now we have identified all the potential HH, LH, LH, and LL.

    Step 4: Implement a simple trial of sell and buy

    We continue our example on Twitter and see how we can perform.

    Our strategy will be as follows.

    • We either have bought stocks for all our money or not. That is, either we have stocks or not.
    • If we do not have stocks, we buy if stock price is above last high, meaning that a HH is coming.
    • If we do have stocks, we sell if stock price is below last low, meaning that a LL is coming.

    This can mean that we enter market in the last of a bull market. If you were to follow the theory complete, it suggest to wait until a bear market changes to a bull market.

    import pandas_datareader as pdr
    import datetime as dt
    
    ticker = pdr.get_data_yahoo("TWTR", dt.datetime(2020,1,1), dt.datetime.now())
    ticker['delta'] = ticker['Close'].diff()
    growth = ticker['delta'] > 0
    ticker['markers'] = growth.diff().shift(-1)
    # We want to remember the last_high and last_low
    # Set to max value not to trigger false buy
    last_high = ticker['Close'].max()
    last_low = 0.0
    # Then setup our account, we can only have stocks or not
    # We have a start balance of 100000 $
    has_stock = False
    balance = 100000
    stocks = 0
    for index, row in ticker.iterrows():
      # Buy and sell orders
      if not has_stock and row['Close'] > last_high:
        has_stock = True
        stocks = balance//row['Close']
        balance -= row['Close']*stocks
      elif has_stock and row['Close'] < last_low:
        has_stock = False
        balance += row['Close']*stocks
        stocks = 0
      # Update the last_high and last_low
      if row['markers']:
        if row['delta'] > 0:
          last_high = row['Close']
        else:
          last_low = row['Close']
    
    print("Dow returns", balance + stocks*ticker['Close'].iloc[-1])
    # Compare this with a simple buy and hold approach.
    buy_hold_stocks = 100000//ticker['Close'].iloc[0]
    buy_hold = 100000 - buy_hold_stocks*ticker['Close'].iloc[0] + buy_hold_stocks*ticker['Close'].iloc[-1]
    print("Buy-and-hold return", buy_hold)
    

    Which results in the following results.

    Dow returns 120302.0469455719
    Buy-and-hold return 117672.44716644287
    

    That looks promising, but it might be just out of luck. Hence, we want to validate with other examples. The results say a return of investment of 20.3% using our Dow theory approach, while a simple buy-and-hold strategy gave 17.7%. This is over the span of less than 8 months.

    The thing you would like to achieve with a strategy is to avoid big losses and not loose out on revenue. The above testing does not justify any clarification on that.

    Step 5: Try out some other tickers to test it

    A first investigation is to check how the algorithm performs on other stocks. We make one small adjustment, as the comparison to buy on day-1, might be quite unfair. If price is low, it an advantage, while if the price is high, it is a big disadvantage. The code below runs on multiple stocks and compare the first buy with a Dow approach (as outlined in this tutorial) with a buy-and-hold approach. The exit of the market might also be unfair.

    import pandas_datareader as pdr
    import datetime as dt
    def dow_vs_hold_and_buy(ticker_name):
      ticker = pdr.get_data_yahoo(ticker_name, dt.datetime(2020,1,1), dt.datetime.now())
      ticker['delta'] = ticker['Close'].diff()
      growth = ticker['delta'] > 0
      ticker['markers'] = growth.diff().shift(-1)
      # We want to remember the last_high and last_low
      # Set to max value not to trigger false buy
      last_high = ticker['Close'].max()
      last_low = 0.0
      # Then setup our account, we can only have stocks or not
      # We have a start balance of 100000 $
      has_stock = False
      balance = 100000
      stocks = 0
      first_buy = None
      for index, row in ticker.iterrows():
        # Buy and sell orders
        if not has_stock and row['Close'] > last_high:
          has_stock = True
          stocks = balance//row['Close']
          balance -= row['Close']*stocks
          if first_buy is None:
            first_buy = index
        elif has_stock and row['Close'] < last_low:
          has_stock = False
          balance += row['Close']*stocks
          stocks = 0
        # Update the last_high and last_low
        if row['markers']:
          if row['delta'] > 0:
            last_high = row['Close']
          else:
            last_low = row['Close']
      dow_returns = balance + stocks*ticker['Close'].iloc[-1]
      # Compare this wiith a simple buy and hold approach.
      buy_hold_stocks = 100000//ticker['Close'].loc[first_buy]
      buy_hold_returns = 100000 - buy_hold_stocks*ticker['Close'].loc[first_buy] + buy_hold_stocks*ticker['Close'].iloc[-1]
      print(ticker_name, dow_returns > buy_hold_returns, round(dow_returns/1000 - 100, 1), round(buy_hold_returns/1000 - 100, 1))
    
    tickers = ["TWTR", "AAPL", "TSLA", "BAC", "KO", "GM", "MSFT", "AMZN", "GOOG", "FB", "INTC", "T"]
    for ticker in tickers:
      dow_vs_hold_and_buy(ticker)
    

    Resulting in the following output.

    TWTR   True  20.3  14.4
    AAPL  False  26.4  52.3
    TSLA   True 317.6 258.8
    BAC    True -16.3 -27.2
    KO     True  -8.2 -14.6
    GM     True   8.9 -15.1
    MSFT  False  26.2  32.1
    AMZN  False  32.8  73.9
    GOOG  False   7.1  11.0
    FB     True  18.3  18.2
    INTC  False -34.9 -18.4
    T     False -25.3 -20.8
    

    This paints a different picture. First, it seems more random if it outperforms the buy-and-hold approach.

    The one performing best is the General Motors Company (GM), but it might be due to unlucky entering of the market. The stock was high in the beginning of the year, and then fell a lot. Hence, here the Dow helped to exit and enter the market correct.

    Intel Corporation (INTC) is working a lot against us. While there is a big loss (-18.4%), it is not saved by our Dow theory algorithm. There was a big loss in stock value 24th of July with 20% from close the day before to open. The Dow cannot save you for situations like that and will sell on the far bottom.

    The Apple (AAPL) is also missing a lot of gain. The stock is in a great growth in 2020, with some challenges in March and after (Corona hit). But looking and buy and sell signals, it hits sell higher than the following buy and losses out on gain.

    Amazon (AMZN) seems to be the same story. Growth in general and hitting buying on higher than previous sell, and loosing out on profit.

    Next steps and considerations

    We have made some broad simplifications in our algorithm.

    • Only consider Close value, while a normal way to find the markers are on a OHLC candlestick diagram.
    • If we used the span of the day price, then we might limit our losses with a stop-loss order earlier.
    • This is not an exact science, and the trends might need a different way to identify them.

    Hence, the above suggest it can be more adjusted to real life.

    Another thing to keep in mind is that you should never make your investment decision on only one indicator or algorithm choice.

    Python for Finance: Unlock Financial Freedom and Build Your Dream Life

    Discover the key to financial freedom and secure your dream life with Python for Finance!

    Say goodbye to financial anxiety and embrace a future filled with confidence and success. If you’re tired of struggling to pay bills and longing for a life of leisure, it’s time to take action.

    Imagine breaking free from that dead-end job and opening doors to endless opportunities. With Python for Finance, you can acquire the invaluable skill of financial analysis that will revolutionize your life.

    Make informed investment decisions, unlock the secrets of business financial performance, and maximize your money like never before. Gain the knowledge sought after by companies worldwide and become an indispensable asset in today’s competitive market.

    Don’t let your dreams slip away. Master Python for Finance and pave your way to a profitable and fulfilling career. Start building the future you deserve today!

    Python for Finance a 21 hours course that teaches investing with Python.

    Learn pandas, NumPy, Matplotlib for Financial Analysis & learn how to Automate Value Investing.

    “Excellent course for anyone trying to learn coding and investing.” – Lorenzo B.

    2 thoughts on “Master Dow Theory with Python Pandas”

    1. Hello Rune. Thanks for this excellent article.
      I understand that to identify highs and lows we must identify when a daily difference goes from positive to negative, but I don’t quite understand how the sentence “ticker[‘markers’] = growth.diff().shift(-1)”, do it.
      in ticker [‘delta’] we know if the daily variation is greater than 0 (True) or not (False). What does the statement” ticker [‘markers’] = growth.diff ()” do?.
      The sentence “ticker[‘markers’] = ticker[‘markers’] ).shift(-1)” I understand that we trade 1 day, as we assume that we react first on a position on the day after the signal.
      I would be grateful if you would help me understand this script, which is very original if I compare it with others that I am browsing on the Internet related to buy and hold strategy.

      Reply
      • Hi E.
        Very good question.

        Well, let us break it down.

        Before that – let’s also remember that DOW theory can be applied on every level. That is, you can look at the monthly variations, daily, hourly, 10 minutely, or any other aspect you want. Here we look at the daily variation.

        We start from the beginning (even though I know you understand it).
        First we get the historic stock prices.

        ticker = pdr.get_data_yahoo("TWTR", dt.datetime(2020,1,1), dt.datetime.now())

        Then we look at the difference. That is yesterday – today price.

        ticker['delta'] = ticker['Close'].diff()

        Then we keep all the ones with postive growth. That is, growth keeps a True statement when delta is positive, and Negative for the indicies it is not positive.

        growth = ticker['delta'] > 0

        Now the next we break down:

        growth.diff()

        This gives True, when we have a change of True/False. That is if we have True, True, False then it will be NaN, False, True.
        Why – because first entry has nothing to compare to, then next compares True to True, this is the same, then False. Then True False, then it changes, True.

        Try with an example – it makes it more easy to understand:
        ticker['delta'] = ticker['Close'].diff()
        ticker['growth'] = growth = ticker['delta'] > 0
        ticker['markers-no-shift'] = growth.diff()
        ticker['markers'] = growth.diff().shift(-1)

        The shift(-1) Simply changes it one backwards.

        Hope it helps,
        Rune

        Reply

    Leave a Comment