Quantifying trend days

In my first blog post I want to show you a way to mathematically define trend days. Trend days are easy to spot (in hindsight) with the naked eye but you probably don't want to weed through 10 years of intraday data manually. Defining a simple "trendiness" measurement can help answering a lot of questions programmatically:

  • Is there a typical behaviour before or after a trend day?
  • Are trend days distributed evenly among the weekdays?
  • Do trend days cluster or are they evenly distributed over time?
  • How trendy is a market?
  • Etc.

Have a look at last Wednesday's DAX chart:

Measuring Intraday Trends

DAX 5 Minute Chart, 14.3.18

The blue line shows the intraday closing price of DAX. The green line represents the trend. It's a linear regression of the closing price on the index (1-168). If you don't know what linear regression is, you can think of it as the best-fitting straight line through all the data points. The red bars show the deviation of the actual closing price and the regression line, in statistical terms these are called residuals. 

These deviations are exactly what we will be looking at. A trend day will have smaller deviations while a choppy day's deviations will be larger. We can use the coefficient of determination (or simply: R2) to measure this deviation. The R2 will always range between 0 and 1. An R2 of 1 means that there's a perfect trend (every closing price is exactly on the green line) while an R2 of 0 means that there is no underlying trend.

We can easily calculate this measure in python with the help of pandas and statsmodels. Assuming you have loaded the data in a pandas dataframe like this:

In [1]: df.head()
Out[1]:
time 08:05:00 08:10:00 08:15:00 08:20:00 08:25:00 08:30:00 08:35:00 08:40:00 08:45:00 08:50:00 ... 21:15:00 21:20:00 21:25:00 21:30:00 21:35:00 21:40:00 21:45:00 21:50:00 21:55:00 22:00:00
date
2007-01-03 6685.4 6685.4 6685.4 6684.4 6684.9 6684.9 6686.9 6686.9 6686.4 6688.4 ... 6642.5 6642.1 6648.5 6646.0 6649.5 6654.5 6655.5 6658.4 6659.4 6657.0
2007-01-04 6663.2 6658.7 6660.2 6660.2 6652.8 6647.8 6646.8 6647.8 6650.3 6650.3 ... 6686.9 6686.4 6684.4 6686.4 6688.9 6689.9 6687.4 6685.4 6687.4 6681.4
2007-01-05 6659.3 6657.4 6655.4 6654.9 6656.4 6656.4 6656.9 6652.9 6656.4 6655.4 ... 6595.1 6595.1 6599.6 6600.1 6596.1 6595.1 6597.6 6597.1 6601.1 6601.6
2007-01-08 6598.6 6602.1 6605.1 6607.6 6612.0 6613.0 6616.0 6615.5 6614.5 6615.0 ... 6630.8 6629.8 6629.8 6628.3 6629.8 6626.3 6627.8 6629.3 6630.3 6628.3
2007-01-09 6649.4 6646.9 6646.9 6644.9 6645.9 6648.9 6650.4 6649.9 6650.9 6650.9 ... 6613.5 6613.0 6611.5 6615.0 6620.4 6620.4 6620.5 6621.4 6621.9 6618.0

5 rows × 168 columns

We then write a function, apply it row-wise to the dataframe and save it in a new column called 'rsquared'.

import statsmodels.api as sm

(...)

def rsquared(row):
    X = sm.add_constant(range(0, len(row.values)))
    y = row.values
    result = sm.OLS(y, X).fit()
    return result.rsquared

df.loc[:, 'rsquared'] = df.apply(rsquared,axis=1)

Now that we have a trend measure for each day of the past 10 years let's have a look at the distribution of the R2 values. Most of the days have no trend at all:

Distribution of R-Squared Values

Distribution of R-Squared Values

We can also look at the most (R2 = 0.96) and the least (R2 = 0) "trendy" day of the data set:

Most trendy DAX day
DAX least trendy

23.11.2007 vs. 26.7.2017

In a follow up post I'll try to answer some of the questions mentioned in the beginning of this post. If you have any question let me know in the comments below or hit me up on Twitter.

Share this!

Latest Posts