Statistical analysis of a stock price
By: Gianluca Malato
Tags: Trading, Stock Market, Data Science, Statistics, Data Visualization

In 2008, for my Bachelor’s Degree in Theoretical Physics, I had to analyze stock prices in order to check the validity of a stock market model. In the following part of the article, I’m going to show you some of those analyses applied to Google stock price using Python.

The code can be found in my GitHub repository here: https://github.com/gianlucamalato/machinelearning/blob/master/Stock_market_analysis.ipynb

Download data

First, we need to get stock data. I’m going to use the yfinance library to download the price time series.

First, we have to install this library.

!pip install yfinance

Then we can import some useful libraries.

import pandas as pd

import numpy as np

import yfinance

import matplotlib.pyplot as plt

from scipy.stats import skew,kurtosis,norm,skewtest,kurtosistest

from statsmodels.graphics.tsaplots import plot_pacf,plot_acf

We can now get Google price data from 2015 to 2020.

name = 'GOOG'

ticker = yfinance.Ticker(name)

df = ticker.history(interval="1d",start="2015-03-15",end="2020-09-10") x = df['Close']

We are now going to work with x object, which contains the daily close price of the stock.

Daily close price

Let’s plot the daily close price.

Image by author

As you can see, there’s quite a nice bullish trend. There are some drawdowns, but the drift seems to be quite positive.

Daily Returns

When you perform the statistical analysis of a stock, it’s very useful to work with its returns and not with the price itself.

The return from one day to another one is the percentage change of the closing price between the two days.

In Python, series objects have the pct_change method that allows us to calculate this quantity. The argument is the lag to use. In this article, I’ll use a 1-day lag.

returns = x.pct_change(1).dropna()

The result is this:

The first number is (x[1]-x[0])/x[0], the second one is (x[2]-x[1])/x[1] and so on.

This is the data we are going to analyze.

The probability distribution of returns

We are now going to calculate some insights about the probability distribution of returns.

Histogram and boxplot

Let’s make a first raw histogram of returns.

plt.hist(returns,bins="rice",label="Daily close price")

plt.legend()

plt.show()

Image by author

As you can see, it’s quite centered around zero and it seems symmetric. However, we can see that this histogram has tails that don’t seem to be neglectable.

Let’s make a boxplot to better understand the distribution of this dataset.

plt.boxplot(returns,labels=["Daily close price"])

plt.show()

As you can see, it’s full of outliers. The interquartile range (i.e. the height of the box) is quite narrow if compared with the distribution total range. This phenomenon is called fat tails and it’s very common in stock analysis.

Main observables

Let’s calculate some observables of our dataset.

The mean value is:

It’s quite similar to zero, but the fact that it’s positive explains the positive drift of the price time series.

Let’s now take a look at the standard deviation:

It’s more than an order of magnitude higher than the mean value. It’s clearly the effect of outliers. In stock price analysis, the standard deviation is a measure of the risk and such a high standard deviation is the reason why stocks are considered risky assets.

Let’s take a look at the median:

It’s not so different from the mean value, so we might think that the distribution is symmetrical.

Let’s check the skewness of the distribution to better assess the symmetry:

It’s positive, so we can assume that the distribution is not symmetrical (i.e. null skewness) and that the right tail as a not neglectable weight.

If we perform a test on the skewness, we find:

The very low p-value suggests that the skewness of the distribution can’t be neglected, so we can’t assume that it’s symmetrical.

Finally, we can measure the kurtosis (scipy normalizes the kurtosis so that it is 0 for a normal distribution)

It’s very different from zero, so the distribution is quite different from a normal one.

A kurtosis test gives us these results:

Again, a very small p-value lets us reject the null hypothesis that the kurtosis is the same as a normal distribution (which is 0).

Are returns normally distributed?

Although the statistically significant high values of kurtosis and skewness already tell us that the returns aren’t normally distributed, a Q-Q plot will give us graphically clear information.

t = np.linspace(0.01,0.99,1000)

q1 = np.quantile(returns,t)

q2 = norm.ppf(t,loc=np.mean(returns),scale=np.std(returns)) plt.plot(q1,q2)

plt.plot([min(q1),max(q1)],[min(q2),max(q2)])

plt.xlim((min(q1),max(q1)))

plt.ylim((min(q2),max(q2)))

plt.xlabel("Daily returns")

plt.ylabel("Normal distribution")

plt.show()

Image by author

The straight line is what we expect for a normal distribution, while the blue line is what we get from our data. It’s clear that the quantiles of our dataset aren’t comparable with the quantiles of a normal distribution with the same mean and standard deviation.

So, returns aren’t normally distributed and this makes models like Geometric Brownian Motion (which assumes normal returns) only an approximation of the reality.

Volatility clustering

Once we have ensured the non-normality of the returns probability distribution, let’s take a look at the raw time series.

plt.plot(returns)

plt.xlabel("Time")

plt.ylabel("Daily returns") plt.show()

Image by author

It’s clear that there are time periods with high volatility and other periods with low volatility. This phenomenon is called volatility clustering and it’s very common in the stock market. Practically speaking, the standard deviation changes during time, making the time series non-stationary.

A closer look at the 20-days rolling standard deviation will make everything clear.

plt.plot(returns.rolling(20).std())

plt.xlabel("Time")

plt.ylabel("20-days rolling standard deviation")

plt.show()

Image by author

It’s clear that it’s not a constant value, but it has spikes and oscillations. The fat tails of the distribution may be caused by these volatility spikes, which create non-neglectable outliers.

Autocorrelation function

Finally, we can plot the partial autocorrelation function, which gives us some ideas about the autocorrelation of the time series and the possibility that this autocorrelation can be used for prediction purposes in some ARIMA model.

plot_pacf(returns,lags=20)

Image by author

It’s clear from this plot that there’s no high correlation among all lags. There are some lags between 5 and 10 that show some correlation, but it’s quite small compared with 1.

So we can easily understand that using some ARIMA models would be quite useless.

Conclusions

In this article, I’ve shown a simple statistical analysis of Google stock price. The returns aren’t normally distributed, since they are skewed and have fat tails. The time series is not stationary because the standard deviation changes over time. The autocorrelation of returns time series is very low at almost any lag, making ARIMA models useless.

So, predicting stock prices using statistics and machine learning is a great challenge. Some results have been achieved using LSTM models, but we are very far from clearly modeling a stock market in a money-making way.