Chapter 13 Parallel computing: An application in Finance

Parallel computing is about reducing computing time by simply dividing tasks and allocating each task to a core of your server or computer.

We will use R here and write parallel code within the context of the “apply” function. We will use the parallel library.

# Generate data and computation on a single core
data <- 1:1e9
data_list <- list("1" = data,
                  "2" = data,
                  "3" = data,
                  "4" = data)# Single core
time_benchmark <- system.time(
  lapply(data_list, mean)
)

This calculation took 23.2 seconds. Below, the same computation is implemented using the multi-core equivalent of lapply (parLapply) from the parallel library:

library(parallel) # Detect the number of available cores and create cluster
cl <- parallel::makeCluster(detectCores())# Run parallel computation
time_parallel <- system.time(
  parallel::parLapply(cl,
                      data_list,
                      mean)
)# Close cluster
parallel::stopCluster(cl)

This calculation took 5.83 seconds.

Another way to parallelize is to use the “foreach” function. To be noted, foreach can be modified to run on a single core by changing %dopar% to %do% :

library(doParallel)
## Loading required package: foreach
## Loading required package: iterators
library(parallel)
library(foreach)# Detect the number of available cores and create cluster
cl <- parallel::makeCluster(detectCores())# Activate cluster for foreach library
doParallel::registerDoParallel(cl)
  time_foreach <- system.time({
  r <- foreach::foreach(i = 1:length(data_list),
                        .combine = rbind) %dopar% {
    mean(data_list[[i]])
  }
})
time_foreach[3]# Stop cluster to free up resources
## elapsed 
##   5.899
parallel::stopCluster(cl)

At 5.83 seconds, the computation time is not significantly different to that of the parLapply implementation.

13.1 An application to portfolio optimisation

# Load parallelisation libraries
library(doParallel)
library(foreach)
library(parallel)# Load finance and optimization libraries
library(PerformanceAnalytics)
## Loading required package: xts
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## 
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
## 
##     legend
library(PortfolioAnalytics)
require(ROI)
## Loading required package: ROI
## Registered S3 method overwritten by 'ROI':
##   method           from              
##   print.constraint PortfolioAnalytics
## ROI: R Optimization Infrastructure
## Registered solver plugins: nlminb, glpk, quadprog.
## Default solver: auto.
## 
## Attaching package: 'ROI'
## The following objects are masked from 'package:PortfolioAnalytics':
## 
##     is.constraint, objective
require(ROI.plugin.glpk)
## Loading required package: ROI.plugin.glpk
require(ROI.plugin.quadprog)
## Loading required package: ROI.plugin.quadprog
require(quadprog)
## Loading required package: quadprog
# Load sample return data from the PerformanceAnalytics library
lookback <- 120 # lookback in months
returns <- tail(PerformanceAnalytics::edhec, lookback)
# Create portfolio object
names_funds <- colnames(returns)
port.obj <- PortfolioAnalytics::portfolio.spec(assets = names_funds)
port.obj <- PortfolioAnalytics::add.constraint(portfolio = port.obj,
                                               type = "full_weight")
port.obj <- PortfolioAnalytics::add.constraint(portfolio = port.obj,
                                               type="long_only")
port.obj <- add.objective(portfolio=port.obj,
                          type='risk',
                          name='ETL',
                          arguments=list(p=0.95))
# Define maximum achievable return
ER_assets <- colMeans(returns)
ER_assets_max <- max(ER_assets)# Calculation of return of minimum risk portfolio
weights_ES_min <- PortfolioAnalytics::optimize.portfolio(
  R = returns,
  portfolio = port.obj,
  optimize_method = "ROI",
  trace = FALSE)$weights
ER_ES_min <- sum(weights_ES_min * ER_assets)# Vector of return targets
n_portfolios <- 500
return_targets <- seq(ER_ES_min,
                      ER_assets_max,
                      length.out = n_portfolios)
# Write optimization function that returns exactly what we need.
optimise <- function(port.obj,
                     return_target) {
  port.obj <- PortfolioAnalytics::add.constraint(
    portfolio = port.obj,
    type="return",
    return_target = return_target)
  out <- PortfolioAnalytics::optimize.portfolio(
    R = returns,
    portfolio = port.obj,
    optimize_method = "ROI",
    trace = FALSE)
  return(c(out$weights, out$objective_measures$ETL))
}
# Activate cluster for foreach library and pass libraries
cl <- parallel::makeCluster(detectCores())
doParallel::registerDoParallel(cl)
  data_frontier_par <- foreach::foreach(
    i = 1:n_portfolios,
    .combine = rbind,
    .packages = c("PortfolioAnalytics")) %dopar% {
      optimise(port.obj, return_targets[i])
    }
  parallel::stopCluster(cl)