Statistical analysis of a stock price | by Gianluca Malato | Towards Data Science

The stock market is always considered a challenge for statistics. Somebody thinks that knowing the statistics of a market lets us beat it and earn money. The reality can be quite different.
In this article, I’m going to show you a statistical analysis of Google stock price.
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
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 pdimport numpy as npimport yfinanceimport matplotlib.pyplot as pltfrom scipy.stats import skew,kurtosis,norm,skewtest,kurtosistestfrom 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.
Let’s plot the daily close price.
As you can see, there’s quite a nice bullish trend. There are some drawdowns, but the drift seems to be quite positive.
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.
We are now going to calculate some insights about the probability distribution of returns.
Let’s make a first raw histogram of returns.
plt.hist(returns,bins="rice",label="Daily close price")plt.legend()plt.show()
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.
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).
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()
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.
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()
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()
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.
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)
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.
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.