Stylized Facts. What does it take to predict the future…
By: Ke Gui
Tags: Stock Market, Data Science, Pandas, Machine Learning, Finance

We always say “let the data speak for themselves”. But data can either shout loud or whispering low. Some data properties are easy to spot, while others are not so obvious and buried in the noise. Like a whisper in your ear, you got to work hard to figure out what they say. Once you reveal the hidden message from data, in some circumstance you may have a chance to predict the future by looking for statistical patterns in currently available data that you believe will continue into the future. In other words, figure out a way in which the future will look very much like the present, only longer. The purpose of this article is to show you how to get those properties from data usually unnoticed but useful.

Before we start, let’s take one minute to think about a simple question of how much information we can draw from a giving set of randomly generated data. Most people probably can give a long list, like max, min, mean, mode, median, variance, standard deviation, range, etc.. Human brain can think abstractly and no other animals can do. Those are the reasons why statistics is useful because it can convert data into information that is meaningful to people. What is more, that deducted information can be utilized to extrapolate some empirical finding, so-called stylized empirical facts in finance.

The definition of stylized facts from reference ¹ at the end of this article says:

“the seemingly random variations of asset prices do share some quite nontrivial statistical properties. Such properties, common across a wide range of instruments, markets and time periods are called stylized empirical facts.”

In plain language, it pretty much says if you want to use the past data to predict the future, the data in the future get to have something in common with the data in the past. Otherwise, nothing makes sense. So, these common patterns from the past and in the future in the data are called stylized facts. There is a wide range set of stylized facts in financial assets as well explained in the reference¹.

With stylized facts in mind, comes another concept, stationary. For times series data, a stationary time series is one whose statistical properties such as mean, variance, autocorrelation, etc. are all constant over time. Most statistical forecasting methods are based on the assumption that the time series can be rendered approximately stationary through the use of mathematical transformations. The predictions for the stationarized series can then be “untransformed,” by reversing whatever mathematical transformations were previously used, to obtain predictions for the original series.

Ok, that is enough talking of “stylized facts” and “stationary”, let’s do some coding to illustrate those two concepts.

1. Data preparation

For consistency, in all the 📈Python for finance series, I will try to reuse the same data as much as I can. More details about data preparation can be found here, here and here.

#import all the libraries

import pandas as pd

import numpy as np

import seaborn as sns

import scipy.stats as scs import yfinance as yf #the stock data from Yahoo Finance import matplotlib.pyplot as plt #set the parameters for plotting

plt.style.use('seaborn')

plt.rcParams['figure.dpi'] = 300 df = yf.download('AAPL',

start = '2000-01-01',

end= '2010-12-31') #download Apple stock price d1 = pd.DataFrame(df['Adj Close'])#create a df with only stock price

d1.rename(columns={'Adj Close':'adj_close'}, inplace=True)

d1['simple_rtn']=d1.adj_close.pct_change()#percentage return

d1['log_rtn'] = np.log(d1.adj_close/d1.adj_close.shift(1))

#log return with 1 day lag d1.head()

One more thing I want to wrangle the data is to remove the outliers, I use simple mean and 2 times of standard deviation to set the boundary.

#get mean and std

mu = d1.describe().loc['mean', 'log_rtn']

sigma = d1.describe().loc['std', 'log_rtn'] condition = (d1['log_rtn'] > mu + sigma * 2) | (d1['log_rtn'] < mu - sigma * 2) #set the condition to be 2 times of std around mean

d1['outliers'] = np.where(condition, 1, 0)#like an if, else

d1.head()

then, I remove all the outliers.

#using pd's bool selection to remove outliers

d1_removed_outliers = d1.loc[d1['outliers'] == 0].iloc[:, :-1]

d1_removed_outliers.head()

d1_removed_outliers.info()

As you can see, there are 2667 data points left out of 2765. For convenience, let’s use the d1 as the DataFrame name again.

d1 = d1_removed_outliers

Again, all the details of how to remove outliers can be found here, here and here.

The difference between the log return and simple percentage return can be found here. In short, small changes in the natural log of a variable are directly interpretable as percentage changes. In another word, percentage changes and natural log change are almost exactly the same as long as the change is small enough(within the range +/- 5%). Indeed, the number in simple_rtn and log_rtn are very close as shown in the table above.

We can check the correlation of simple_rtn and log_rtn,

#calculate the pearson correlation

d1[['simple_rtn', 'log_rtn']].corr()

These two returns are highly correlated. It is even clear from the heatmap:

#draw heatmap with seaborne

cmap = sns.diverging_palette(220, 20, as_cmap=True)

ax = sns.heatmap(corr, annot=True, cmap=cmap,

square=True, linewidths=3,

linecolor='w')

ax.set_title('Autocorrelation Plots', fontsize=26)

sns.set(font_scale=2);

One nice thing about Pandas is that it is very easy to get those descriptive statistics straight away.

d1.describe().round(4)

2. Normal (Gaussian) distribution of returns

One of the most discussed stylized facts is the normal (Gaussian) distribution of returns. A large number of important financial models rest on the assumption that returns of stocks are normally distributed, which you will see at the end of this article, may not be the case. Therefore, the normal distribution can be considered one of the most important distribution in finance and one of the major statistical building blocks of many financial theories.

Let’s have a look at the normality of adjusted price, percentage return and natural logarithm return. First, we define a function to extract the descriptive statistics from d1.describe()

#extract all the stats from describe() function

def extract_data_stats(col):

d_stat = col.describe()

mu = d_stat['mean']

sigma = d_stat['std']

rtn_range = np.linspace(d_stat['min'], d_stat['max'], num=1000)

norm_pdf = scs.norm.pdf(rtn_range, loc=mu, scale=sigma)



return mu, sigma, rtn_range, norm_pdf

With the mean, standard deviation and normal Probability Density Function (PDF) ready, we can plot the histogram and PDF.

#draw the histogram with Probability Density Function

def draw_hist(col, xlim=(-0.2, 0.2)):

mu, sigma, rtn_range, norm_pdf = extract_data_stats(col) sns.distplot(col, kde=True, norm_hist=True, label='Hist')

plt.plot(rtn_range, norm_pdf, 'r', lw=3,

label=f'N({mu:.3f}, {sigma**2:.4f})')

plt.axvline(x=0, c='c',linestyle='--', lw=3)

plt.title(f'Distribution of {col.name}', fontsize=24)

plt.xlim(xlim)

plt.legend(loc='upper right', fontsize=20,

frameon=True,fancybox=True,

framealpha=1, shadow=True, borderpad=1);

The histogram and PDF of percentage return.

draw_hist(d1.simple_rtn)

The histogram and PDF of natural logarithm return.

draw_hist(d1.log_rtn)

The histogram and PDF of adjusted price.

draw_hist(d1.adj_close,xlim=(-10,50))

Clearly, the stock price has trend or cycles which cause it far from a normal distribution. Whereas, the log return and percentage return are very similar and close to normal. But can the normality be tested?