Pada studi ini kita akan menganalisa distribusi probabilitas dari return. Orang sering menggunakan distribusi normal (Gaussian) untuk menganalisa return, padahal distribusi dari return biasanya bukan distribusi normal. Kesalahan ini dapat berakibat fatal pada hasil analisisnya.
In [47]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import display, Markdown, HTML
%matplotlib inline
FIGSIZE=(8,4)
returns = pd.read_csv('SmallLargeCapReturns.csv', index_col='date', parse_dates=True)
returns.index = returns.index.to_period('M')
returns.head()
Out[47]:
In [32]:
N = len(returns)
mean = returns['SmallCap'].mean()
std = returns['SmallCap'].std()
print('N: {}, mean: {}, std: {}'.format(N, mean, std))
Setelah itu kita bisa mensintesis data buatan dengan distribusi Normal dengan kedua parameter di atas, lalu kita simpan sebagai Pandas Series agar mudah di-plot, sebagai berikut.
In [51]:
np.random.seed(11)
norm_returns = pd.Series(np.random.normal(mean, std, N))
Kita bisa verifikasi mean dan standard deviasinya sesuai (catatan: ketidaksesuaian mean dan std dari random samples ini biasanya disebabkan karena jumlah samples yang kita sintesis tidak banyak. Semakin besar N, maka mean dan std akan semakin mendekati parameternya):
In [53]:
print('Mean: {}, std: {}'.format(norm_returns.mean(), norm_returns.std()))
Cukup dekat.
Selanjutnya kita akan gambarkan return bulanan yang kita sintesa di atas. Dalam grafik di bawah, kita gambarkan juga garis oranye yang mencerminkan standard deviasi +3 dan -3, yang mencerminkan 0.9973 dan 0.0027 quantile dari data kita. Artinya, hanya ada kemungkinan 0.54% (=0.0027*2) bahwa sample-nya di atas atau di bawah garis ini. Dalam konteks return bulanan, kejadian ini hanya akan terjadi setiap 15 tahun (=1/0.0054).
In [54]:
fig, ax = plt.subplots(figsize=(15,6))
x = range(N)
ax.bar(x, norm_returns, label='Monthly Return')
ax.axhline(mean + 3 * std, color='C1', label='0.997 quantile')
ax.axhline(mean - 3 * std, color='C1', label='0.003 quantile')
ax.set_title('Normally Distributed Returns (Simulated)')
ax.legend()
Out[54]:
Terlihat di atas bahwa return bulanan berapa di luar batas +/- 3 SD hanya dua kali.
Kalau kita plot distribusinya sebagai histogram:
In [55]:
norm_returns.hist()
Out[55]:
Maka tampak histogram yang bentuknya mirip dengan distribusi normal. Kalaupun ada perbedaan, ini dikarenakan jumlah sampel yang kita sintesis tidak cukup banyak.
In [40]:
fig, ax = plt.subplots(figsize=(15,6))
x = range(len(returns))
ax.bar(x, returns['SmallCap'], label='Monthly returns')
ax.axhline(mean + 3 * std, color='C1', label='0.997 quantile')
ax.axhline(mean - 3 * std, color='C1', label='0.003 quantile')
ax.set_title('Actual Monthly Returns')
ax.legend()
Out[40]:
Terlihat bahwa return yang aktual lebih banyak melampaui batas +/- 3 SD. Kita bisa menghitung berapa kali dia melampai batas dengan kode berikut.
In [45]:
plus3sd = returns['SmallCap'].mean() + 3 * returns['SmallCap'].std()
minus3sd = returns['SmallCap'].mean() - 3 * returns['SmallCap'].std()
print('+3 SD: {}, -3 SD: {}'.format(plus3sd, minus3sd))
outside = returns[(returns['SmallCap'] > plus3sd) | (returns['SmallCap'] < minus3sd)]
outside['SmallCap']
Out[45]:
Kita juga bisa plot distribusinya sebagai histogram:
In [49]:
returns['SmallCap'].hist()
Out[49]:
Histogram di atas bukanlah distribusi Normal, karena tidak simetris dan juga mempunyai ekor yang panjang (fat tail). Kedua karakteristik ini disebut skewness dan kurtosis.
Dari studi di atas, tampaknya kita harus mempelajari lebih jauh dari sekedar melihat mean dan variance. Kita harus melihat higher order moments, dalam hal ini terutama adalah skewness dan kurtosis.
Skewness adalah ukuran dari simetri sebuah distribusi. Kita tahu bahwa distribusi normal adalah simetris; probabilitas untuk mendapatkan suatu nilai di atas dan di bawah mean (misalnya +1% dan -1% di atas/bawah mean) adalah sama. Jika distribusinya mempunyai skew yang negatif, artinya kemungkinan untuk mendapatkan sampel di bawah mean lebih besar dari pada kemungkinan mendapatkan sampel di atas mean. Distribusi Normal mempunyai nilai skewness nol.
Kurtosis adalah ukulan ketebalan dari ekor distribusi. Kita tahu bahwa distribusi Normal mempunyai ekor yang sangat tipis. Distribusi probabilitasnya turun secara drastis menuju nol. Distribusi Normal mempunyai nilai kurtosis tiga. Nilai kurtosis lebih besar dari tiga berarti distribusi tersebut mempunyai ekor yang tebal (fat tail).
Excess kurtosis adalah nilai kurtosis sebuah distribusi dikurangi tiga. Untuk distribusi normal, excess kurtosis bernilai nol.
In [56]:
from scipy.stats import kurtosis, skew
sk = skew(norm_returns)
kr = kurtosis(norm_returns)
print('Skewness: {}, excess kurtosis: {}'.format(sk, kr))
Seharusnya kedua nilai di atas adalah nol, tapi sekali lagi hal ini disebabkan karena jumlah sampel kita cukup sedikit.
Sekarang mari kita hitung untuk return dari SmallCap.
In [57]:
sk = skew(returns['SmallCap'])
kr = kurtosis(returns['SmallCap'])
print('Skewness: {}, excess kurtosis: {}'.format(sk, kr))
Seperti terlihat, angka-angkanya jauh menyimpang dari nol, sehingga dapat disimpulkan distribusinya bukan Normal.
Tes Jargue Bera dapat digunakan untuk mentes apakah suatu sampel data mempunyai skewness dan kurtosis yang cocok dengan distribusi Normal.
Tes ini juga sudah diimplementasikan di Python. Catatan: Jargue-Bera test membutuhkan data paling tidak 2000 sampel, sedangkan data kita hanya 1100, jadi hasilnya mungkin tidak sepenuhnya benar.
In [86]:
from scipy.stats import jarque_bera
_, pval = jarque_bera(norm_returns)
print('Distribusinya {} (pval: {:.2f})'.format('normal' if pval >= 0.05 else 'bukan normal', pval))
_, pval = jarque_bera(returns['SmallCap'])
print('Distribusinya {} (pval: {:.2f})'.format('normal' if pval >= 0.05 else 'bukan normal', pval))
Sebagian besar materi di sini diambil dari materi minggu 1 dari MOOC Introduction to Portfolio Construction and Analysis with Python.
In [ ]: