Risk Management in finance is one of the most common case studies
for Grid Computing, and Value-at-Risk is most widely used risk measure.
In this article I’m going to show how to scale-out Value-at-Risk calculation to multiple nodes with latest Akka middleware.
In Part 1 I’m describing the problem and single-node solution, and in Part 2 I’m scaling it to multiple nodes.
[Part 1] Introduction to Value at Risk calculation
Go to Part 2 where VaR calculation scaled-out to multiple nodes.
What is Value-at-Risk
In financial mathematics and financial risk management, value at risk (VaR) is a widely used risk measure of the risk of loss on a specific portfolio of financial assets. For a given portfolio, probability and time horizon, VaR is defined as a threshold value such that the probability that the mark-to-market loss on the portfolio over the given time horizon exceeds this value (assuming normal markets and no trading in the portfolio) is the given probability level.
You can continue with reading amazing set of articles “Introduction to Grid Computing for Value At Risk calculation” where VaR described in more details
and explained why it’s a perfect fit for grid computing and spreading calculation across multiple nodes. Also there you can find examples for GridGain and Hadoop. In this article I’m going to use Akka.
Data Model
Let’s define simple model for supported instruments:
sealedtraitPricingErrorobjectPricingError{caseclassMissingMarketFactors(factors:NonEmptyList[MarketFactor])extendsPricingError}@implicitNotFound(msg="Can't find pricer for instrument type '${I}'")traitPricer[I<:Instrument]{defprice(i:I)(implicitm:MarketFactors):PricingError\/Double}objectPricerextendsPricerImplicitstraitPricerImplicits{implicitobjectEquityPricerextendsPricer[Equity]{// ...}implicitobjectOptionPricerextendsPricer[EquityOption]{// Use Black-Scholes formula to price Option}}
Market Risk Calculator
Portfolio Market Risk depends on market factors forecast. I’m going to construct market factors forecast for one day horizon from historical market data:
Monte Carlo Simulation for Market Risk Calculation
Monte Carlo simulation performs risk analysis by building models of possible results by substituting a range of values—a probability distribution—for any factor that has inherent uncertainty. It then calculates results over and over, each time using a different set of random values from the probability functions. Depending upon the number of uncertainties and the ranges specified for them, a Monte Carlo simulation could involve thousands or tens of thousands of recalculations before it is complete. Monte Carlo simulation produces distributions of possible outcome values. …
Abstract Monte Carlo Risk Calculator
I will use scalaz-stream for splitting calculation to independent tasks, running them concurrently and aggregating results.
Some code is omitted. Full source code on Github –>>>link
traitPortfolioValueSimulation{self:MarketFactorsModulewithMonteCarloMarketRiskCalculator=>/** * Scalaz-Stream Channel that for given market factors generator * runs defined number of simulations and produce MarketRisk */defsimulation(portfolio:Portfolio,simulations:Int):Channel[Task, MarketFactorsGenerator, Simulations]}/** * @concurrencyLevel Number of simulation tasks running at the same time */abstractclassMonteCarloMarketRiskCalculator(simulations:Int=1000000,splitFactor:Int=10,concurrencyLevel:Int=10)extendsMarketRiskCalculatorwithMarketDataModulewithMarketFactorsModulewithPortfolioValueSimulation{calculator=>caseclassSimulations(simulations:Vector[Double])classMarketRisk(initialValue:Double,simulations:Simulations){defVaR(p:Double):Double=...defconditionalVaR(p:Double):Double=...}privatevalP=ProcessdefmarketRisk(portfolio:Portfolio,date:LocalDate):MarketRisk={// Get initial Portfolio valueimplicitvalinitialFactors=marketFactors(date)valinitialPortfolioValue=PortfolioPricer.price(portfolio).fold(err=>sys.error(s"Failed price portfolio: $err"),identity)// Run portfolio values simulationvaloneSimulation=simulations/splitFactorvalsimulationChannel=simulation(portfolio,oneSimulation)valgenerator=oneDayMarketFactors(portfolio,date)valprocess=P.range(0,splitFactor).map(_=>generator).concurrently(concurrencyLevel)(simulationChannel).runFoldMap(identity)// Produce market-risk object from initial value and simulated valuesnewMarketRisk(initialPortfolioValue,process.run)}}
Running simulation on a single node
Here is straightforward PortfolioValueSimulation implementation, that runs simulation tasks in a separate thread pool in a single JVM:
1234567891011121314151617181920
traitSingleNodePortfolioValueSimulationextendsPortfolioValueSimulation{self:MarketFactorsModulewithMonteCarloMarketRiskCalculator=>defsimulation(portfolio:Portfolio,simulations:Int)=channel[MarketFactorsGenerator, Simulations]{generator=>// calculate portfolio prices for generated market factorsvalprocess=generator.factors.take(simulations).map{implicitfactors=>PortfolioPricer.price(portfolio).fold(err=>sys.error(s"Failed to price portfolio: $err"),identity)}// Fork simulations into thread poolTask.fork{log.debug(s"Simulate $simulations portfolio values for $portfolio")process.runLog.map(portfolioValues=>Simulations(portfolioValues.toVector))}(executor)}}