Introduction

This notebook is an implementation of Jón Daníelsson's Financial Risk Forecasting (Wiley, 2011) in Julia 0.6.2, with annotations and introductory examples. The introductory examples (Appendix) are similar to Appendix B/C in the original book, with an emphasis on the differences between R/MATLAB and Julia.

Bullet point numbers correspond to the R/MATLAB Listing numbers in the original book, referred to henceforth as FRF.

More details can be found at the book website: https://www.financialriskforecasting.com/

Last updated: June 2018

Copyright 2011, 2016, 2018 Jón Daníelsson. This code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. The GNU General Public License is available at: https://www.gnu.org/licenses/.

Appendix: An Introduction to Julia

Created in Julia 0.6.2 (June 2018)

  • J.1: Entering and Printing Data
  • J.2: Vectors, Matrices and Sequences
  • J.3: Importing Data (to be updated)
  • J.4: Basic Summary Statistics
  • J.5: Calculating Moments
  • J.6: Basic Matrix Operations
  • J.7: Statistical Distributions
  • J.8: Statistical Tests
  • J.9: Time Series
  • J.10: Loops and Functions
  • J.11: Basic Graphs
  • J.12: Miscellaneous Useful Functions
In [1]:
# Entering and Printing Data in Julia
# Listing J.1
# Last updated June 2018
#
#

x = 10             # assign x the value 10
println(x)         # print x

## println() puts next output on new line, while print() doesn't
10
In [2]:
# Vectors, Matrices and Sequences in Julia
# Listing J.2
# Last updated June 2018
#
#

y = [1,3,5,7,9]     # lists in square brackets are stored as arrays

println(y)

println(y[3])       # calling 3rd element (Julia indices start at 1)

println(size(y))    # size of y

println(length(y))  # as expected, y has length 5

v = fill!(Array{Float64}(2,3),NaN) # 2x3 Float64 matrix of NaNs

println(v)          # Julia prints matrices in a single line

println(size(v))    # as expected, v is size (2,3)

w = repmat([1,2,3], 2, 3) # repeats matrix twice by rows, thrice by columns

println(w)

s = 1:10            # s is an sequence which one can loop across

println(collect(s)) # return sequence elements as an array
[1, 3, 5, 7, 9]
5
(5,)
5
[NaN NaN NaN; NaN NaN NaN]
(2, 3)
[1 1 1; 2 2 2; 3 3 3; 1 1 1; 2 2 2; 3 3 3]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
In [3]:
# Importing Data in Julia
# Listing J.3
# Last updated June 2018
#
#

## There are many data sources for financial data, for instance
## Yahoo Finance, AlphaVantage and Quandl. However, some of the
## free data sources have numerous issues with accuracy and
## handling of missing data, so only CSV importing is shown here.
##
## For csv data, one can use the package CSV to read it
##
## Example:
## using CSV;
## data = CSV.read("data.csv", nullable = false) 
## nullable = false avoids type problems involving NullableArray types
In [4]:
# Basic Summary Statistics in Julia
# Listing J.4
# Last updated June 2018
#
#

y = [3.14,15,9.26,5]

println("sum: ", sum(y))         # return sum of all elements of y
println("product: ", prod(y))    # return product of all elements of y
println("max: ", maximum(y))     # return maximum value of y
println("min: ", minimum(y))     # return minimum value of y
println("mean: ", mean(y))       # arithmetic mean
println("median: ", median(y))   # median
println("variance: ", var(y))    # variance
println("cov_matrix: ", cov(y))  # covar matrix = variance for single vector
println("cor_matrix: ", cor(y))  # corr matrix = [1] for single vector
println(sort(y))                 # sorts y in ascending order
println(log.(y))                 # natural log, note . denotes elementwise operation
sum: 32.4
product: 2180.73
max: 15.0
min: 3.14
mean: 8.1
median: 7.13
variance: 27.722400000000004
cov_matrix: 27.722400000000004
cor_matrix: 1.0
[3.14, 5.0, 9.26, 15.0]
[1.14422, 2.70805, 2.2257, 1.60944]
In [5]:
# Calculating Moments in Julia
# Listing J.5
# Last updated June 2018
#
#

using StatsBase;

println("mean: ", mean(y))          # mean
println("variance: ", var(y))       # variance
println("std dev: ", std(y))        # unbiased standard deviation
println("skewness: ", skewness(y))  # skewness
println("kurtosis: ", kurtosis(y))  # EXCESS kurtosis (note the different default)
mean: 8.1
variance: 27.722400000000004
std dev: 5.265206548655049
skewness: 0.47004939528995604
kurtosis: -1.2846861061904815
In [6]:
# Basic Matrix Operations in Julia
# Listing J.6
# Last updated June 2018
#
#

z = Matrix([[1 2];[3 4]])   # z is a 2 x 2 matrix
x = Matrix([1 2])           # x is a 1 x 2 matrix

## Note: z * x is undefined since the two matrices are not conformable

println(z * x')             # this evaluates to a 2 x 1 matrix

b = vcat(z,x)               # "stacking" z and x vertically
c = hcat(z,x')              # "stacking" z and x' horizontally 

## Note: dimensions must match along the combining axis
[5; 11]
Out[6]:
2×3 Array{Int64,2}:
 1  2  1
 3  4  2
In [7]:
# Statistical Distributions in Julia
# Listing J.7
# Last updated June 2018
#
#

## Julia has a wide range of functions contained in the package Distributions.jl
## Vectorized versions of the functions are used here as they are relevant for FRF

using Distributions;

q = collect((-3:1:3))              # specify a set of values

p = collect((0.1:0.1:0.9))         # specify a set of probabilities

println(quantile.(Normal(0,1),p))  # element-wise inverse Normal quantile

println(cdf.(TDist(4), q))         # element-wise cdf calculation under Student-t(4)

println(pdf.(Chisq(2), q))         # element-wise pdf calculation under Chisq(2)

## Similar syntax for other dists, e.g. Bernoulli(p), Binomial(n,p), Poisson(λ)

## For full list of supported distributions, see Distributions.jl documentation

## One can also obtain pseudorandom samples from distributions using rand()

x = rand(TDist(5), 100)            # Sampling 100 times from TDist with 5 df

y = rand(Normal(0,1), 50)          # Sampling 50 times from a standard normal 

## Given data, we obtain MLE estimates of parameters with fit_mle():

fit_mle(Normal, x)                 # Fitting x to normal dist     

## Some distributions like the Student-t cannot be fitted yet (as of June 2018)
## Supported dists: https://juliastats.github.io/Distributions.jl/latest/fit.html#Applicable-distributions-1
[-1.28155, -0.841621, -0.524401, -0.253347, 0.0, 0.253347, 0.524401, 0.841621, 1.28155]
[0.019971, 0.0580583, 0.18695, 0.5, 0.81305, 0.941942, 0.980029]
[0.0, 0.0, 0.0, 0.5, 0.303265, 0.18394, 0.111565]
Out[7]:
Distributions.Normal{Float64}(μ=-0.11106507393805169, σ=1.2017396832884832)
In [8]:
# Statistical Tests in Julia
# Listing J.8
# Last updated June 2018
#
#

srand(100)
x = rand(TDist(5), 500)          # Create hypothetical dataset x

## We use the package HypothesisTests

using HypothesisTests;

println(JarqueBeraTest(x))       # Jarque-Bera test for normality
println(LjungBoxTest(x,20))      # Ljung-Box test for serial correlation
Jarque-Bera normality test
--------------------------
Population details:
    parameter of interest:   skewness and kurtosis
    value under h_0:         0 and 3
    point estimate:          -0.18495547797244077 and 4.685709971136253

Test summary:
    outcome with 95% confidence: reject h_0
    one-sided p-value:           3.3556561381445264e-14

Details:
    number of observations:         500
    JB statistic:                   62.05108796075508

Ljung-Box autocorrelation test
------------------------------
Population details:
    parameter of interest:   autocorrelations up to lag k
    value under h_0:         all zero
    point estimate:          NaN

Test summary:
    outcome with 95% confidence: fail to reject h_0
    one-sided p-value:           0.3507455225683908

Details:
    number of observations:         500
    number of lags:                 20
    degrees of freedom correction:  0
    Q statistic:                    21.812961541354174

In [9]:
# Time Series in Julia
# Listing J.9
# Last updated June 2018
#
#

srand(100)
x = rand(TDist(5), 60)           # Create hypothetical dataset x

using Plots, StatsBase;          # refer to Listing 0.11 for Plots.jl

acf = autocor(x, 1:20)           # autocorrelation for lags 1:20
pacf = autocor(x, 1:20)          # partial autocorrelation for lags 1:20

plot(bar(acf), bar(pacf))        # plotting the ACF/PACF using Plots.jl
Out[9]:
5 10 15 20 -0.3 -0.2 -0.1 0.0 0.1 0.2 y1 5 10 15 20 -0.3 -0.2 -0.1 0.0 0.1 0.2 y1
In [10]:
# Loops and Functions in Julia
# Listing J.10
# Last updated June 2018
#
#

## We demonstrate how loops and functions work in Julia with some examples
## Main differences from Python

## 1) No semicolons on the first line of loops/functions
## 2) insert "end" after the last line of loops/functions
## 3) Note: difference in range(.) function between Python and Julia (see below)

## For loops

for i in range(3,5)        # NOTE: range(start,n) unusual!
    println(i^2)           # where n = number of terms      
    end                    # this iterates over [3,4,5,6,7]

## If-else loops

X = 10

if X % 3 == 0
    println("X is a multiple of 3")
else
    println("X is not a multiple of 3")
end

## Functions (example: a simple excess kurtosis function)

function excess_kurtosis(x, excess = 3)::Float64      # excess optional, default = 3
    m4 = mean((x-mean(x)).^4)                         # element-wise exponentiation .^
    excess_kurt = m4/(std(x)^4) - excess
    return excess_kurt
end

srand(100)
x = rand(TDist(5), 60)           # Create hypothetical dataset x

excess_kurtosis(x)               

## Note: we have forced output to be of type Float64 by the type declaration above
9
16
25
36
49
X is not a multiple of 3
Out[10]:
0.05052031501895815
In [11]:
# Basic Graphs in Julia
# Listing J.11
# Last updated June 2018
#
#

## For the simple plots in FRF we use Plots.jl for plotting
## Full documentation at http://docs.juliaplots.org/latest/

## By default, Plots.jl uses the GR backend, sufficient for plots done in FRF
## Alternative backends are also available, e.g. Plotly, PlotlyJS

y = rand(Normal(0,1), 50)

using Plots;

## plot barplot, lineplot, histogram, scatterplot of y

return plot(bar(y), plot(y), histogram(y), scatter(y))

## Wrapping plot(...) around multiple plots allows for automatic subplotting
## This can, of course, be manually specified too
## Plot individual graphs using histogram(y), bar(y) etc. directly

## More examples using GR (plus syntax for customizations) can be found online:
## http://docs.juliaplots.org/latest/examples/gr/
Out[11]:
0 10 20 30 40 50 -1 0 1 2 y1 10 20 30 40 50 -1 0 1 2 y1 -2 -1 0 1 2 3 0 5 10 15 y1 0 10 20 30 40 50 -1 0 1 2 y1
In [12]:
# Miscellaneous Useful Functions in Julia
# Listing J.12
# Last updated June 2018
#
#

## 1) To convert objects from one type to another, use convert(Type, object)
##    To check type, use typeof(object)

x = 8.0

println(typeof(x))

x = convert(Int, 8.0)

println(typeof(x))

## 2) To type Greek letters, type \ + name + Tab in succession
##    e.g. \gammaTab gives you γ and \GammaTab gives you Γ 
##
##    Greek letters are sometimes essential in retrieving parameters from functions
##    e.g. res = mle_fit(Normal, x) will return an object res of type Distribution
##    with fitted parameters res.μ and res.σ
Float64
Int64

Chapter 1: Financial Markets, Prices and Risk

  • 1.1/1.2: Loading hypothetical stock prices, converting to returns, plotting returns
  • 1.3/1.4: Summary statistics for returns timeseries
  • 1.5/1.6: Autocorrelation function (ACF) plots, Ljung-Box test
  • 1.7/1.8: Quantile-Quantile (QQ) plots
  • 1.9/1.10: Correlation matrix between different stocks
In [65]:
# Download S&P 500 data in Julia
# Listing 1.1/1.2
# Last updated June 2018
#
#

using CSV;
price = CSV.read("index.csv", nullable = false);
y = diff(log.(price[:,1]));

using Plots;
plot(y)
Out[65]:
1000 2000 3000 4000 5000 -0.10 -0.05 0.00 0.05 0.10 y1
In [14]:
# Sample statistics in Julia
# Listing 1.3/1.4
# Last updated June 2018
#
#

using StatsBase;

println(std(y))
println(minimum(y))
println(maximum(y))
println(skewness(y))
println(kurtosis(y))
println(autocor(y, 1:20))
println(autocor(y.^2, 1:20))

using HypothesisTests;

println(JarqueBeraTest(y))
println(LjungBoxTest(y,20))
println(LjungBoxTest(y.^2, 20))
0.010005728643763059
-0.10195548627302298
0.10673589502911707
0.1526332698963327
13.981171461091993
[0.0148155, -0.00663134, 0.00897934, 0.0389366, -0.0420219, 0.0235105, 0.047527, 0.0113021, -0.0397542, -0.0123753, 0.0182225, 0.0153466, -0.0260407, -0.00724454, 0.0361944, -0.0458169, -0.00716777, 0.0489893, 0.0146028, -0.0275982]
[0.199187, 0.144605, 0.123645, 0.204692, 0.167127, 0.193404, 0.07918, 0.119441, 0.0590163, 0.156273, 0.139661, 0.143016, 0.119192, 0.120364, 0.115931, 0.138058, 0.228529, 0.142721, 0.116203, 0.168162]
Jarque-Bera normality test
--------------------------
Population details:
    parameter of interest:   skewness and kurtosis
    value under h_0:         0 and 3
    point estimate:          0.15263326989633252 and 16.981171461091993

Test summary:
    outcome with 95% confidence: reject h_0
    one-sided p-value:           0.0

Details:
    number of observations:         5676
    JB statistic:                   46251.44013954815

Ljung-Box autocorrelation test
------------------------------
Population details:
    parameter of interest:   autocorrelations up to lag k
    value under h_0:         all zero
    point estimate:          NaN

Test summary:
    outcome with 95% confidence: reject h_0
    one-sided p-value:           1.8088013487193486e-11

Details:
    number of observations:         5676
    number of lags:                 20
    degrees of freedom correction:  0
    Q statistic:                    93.48812639805915

Ljung-Box autocorrelation test
------------------------------
Population details:
    parameter of interest:   autocorrelations up to lag k
    value under h_0:         all zero
    point estimate:          NaN

Test summary:
    outcome with 95% confidence: reject h_0
    one-sided p-value:           0.0

Details:
    number of observations:         5676
    number of lags:                 20
    degrees of freedom correction:  0
    Q statistic:                    2543.047730864134

In [15]:
# ACF plots and the Ljung-Box test in Julia
# Listing 1.5/1.6
# Last updated June 2018
#
#

using Plots;

q1 = autocor(y, 1:20)
q2 = autocor(y.^2, 1:20)

plot(bar(q1), bar(q2))
Out[15]:
5 10 15 20 -0.04 -0.02 0.00 0.02 0.04 y1 5 10 15 20 0.00 0.05 0.10 0.15 0.20 y1
In [16]:
# QQ plots in Julia
# Listing 1.7/1.8
# Last updated June 2018
#
#

using Plots, StatPlots, Distributions;

plot(qqplot(Normal,float(y),qqline=:quantile), qqplot(TDist(5),float(y),qqline=:quantile))
Out[16]:
-0.03 -0.02 -0.01 0.00 0.01 0.02 0.03 -0.05 0.00 0.05 0.10