{-# LANGUAGE RecursiveDo #-}
-- This financial model is described in
-- Vensim 5 Modeling Guide, Chapter Financial Modeling and Risk.
--
-- It illustrates how you can use the Monte-Carlo simulation
-- and define external parameters. Here the system of recursive
-- diffential equations is used but the paradigm can be any
-- supported by Aivika including DES or agent-base modeling
-- or their combination.
--
-- To enable the parallel simulation, you should compile it
-- with option -threaded and then pass in other options +RTS -N2 -RTS
-- to the executable if you have a dual core processor without
-- hyper-threading. Also you can increase the number
-- of parallel threads via option -N if you have a more modern
-- processor.
import Control.Monad
import Simulation.Aivika
import Simulation.Aivika.SystemDynamics
import Simulation.Aivika.Experiment
import Simulation.Aivika.Experiment.Chart
-- | The model parameters.
data Parameters =
Parameters { paramsTaxDepreciationTime :: Parameter Double,
paramsTaxRate :: Parameter Double,
paramsAveragePayableDelay :: Parameter Double,
paramsBillingProcessingTime :: Parameter Double,
paramsBuildingTime :: Parameter Double,
paramsDebtFinancingFraction :: Parameter Double,
paramsDebtRetirementTime :: Parameter Double,
paramsDiscountRate :: Parameter Double,
paramsFractionalLossRate :: Parameter Double,
paramsInterestRate :: Parameter Double,
paramsPrice :: Parameter Double,
paramsProductionCapacity :: Parameter Double,
paramsRequiredInvestment :: Parameter Double,
paramsVariableProductionCost :: Parameter Double }
-- | The default model parameters.
defaultParams :: Parameters
defaultParams =
Parameters { paramsTaxDepreciationTime = 10,
paramsTaxRate = 0.4,
paramsAveragePayableDelay = 0.09,
paramsBillingProcessingTime = 0.04,
paramsBuildingTime = 1,
paramsDebtFinancingFraction = 0.6,
paramsDebtRetirementTime = 3,
paramsDiscountRate = 0.12,
paramsFractionalLossRate = 0.06,
paramsInterestRate = 0.12,
paramsPrice = 1,
paramsProductionCapacity = 2400,
paramsRequiredInvestment = 2000,
paramsVariableProductionCost = 0.6 }
-- | Random parameters for the Monte-Carlo simulation.
randomParams :: IO Parameters
randomParams =
do averagePayableDelay <- memoParameter $ randomUniform 0.07 0.11
billingProcessingTime <- memoParameter $ randomUniform 0.03 0.05
buildingTime <- memoParameter $ randomUniform 0.8 1.2
fractionalLossRate <- memoParameter $ randomUniform 0.05 0.08
interestRate <- memoParameter $ randomUniform 0.09 0.15
price <- memoParameter $ randomUniform 0.9 1.2
productionCapacity <- memoParameter $ randomUniform 2200 2600
requiredInvestment <- memoParameter $ randomUniform 1800 2200
variableProductionCost <- memoParameter $ randomUniform 0.5 0.7
return defaultParams { paramsAveragePayableDelay = averagePayableDelay,
paramsBillingProcessingTime = billingProcessingTime,
paramsBuildingTime = buildingTime,
paramsFractionalLossRate = fractionalLossRate,
paramsInterestRate = interestRate,
paramsPrice = price,
paramsProductionCapacity = productionCapacity,
paramsRequiredInvestment = requiredInvestment,
paramsVariableProductionCost = variableProductionCost }
-- | This is the model itself that returns experimental data.
model :: Parameters -> Simulation ExperimentData
model params =
mdo let getParameter f = liftParameter $ f params
-- the equations below are given in an arbitrary order!
bookValue <- integ (newInvestment - taxDepreciation) 0
let taxDepreciation = bookValue / taxDepreciationTime
taxableIncome = grossIncome - directCosts - losses
- interestPayments - taxDepreciation
production = availableCapacity
availableCapacity = ifDynamics (time .>=. buildingTime)
productionCapacity 0
taxDepreciationTime = getParameter paramsTaxDepreciationTime
taxRate = getParameter paramsTaxRate
accountsReceivable <- integ (billings - cashReceipts - losses)
(billings / (1 / averagePayableDelay
+ fractionalLossRate))
let averagePayableDelay = getParameter paramsAveragePayableDelay
awaitingBilling <- integ (price * production - billings)
(price * production * billingProcessingTime)
let billingProcessingTime = getParameter paramsBillingProcessingTime
billings = awaitingBilling / billingProcessingTime
borrowing = newInvestment * debtFinancingFraction
buildingTime = getParameter paramsBuildingTime
cashReceipts = accountsReceivable / averagePayableDelay
debt <- integ (borrowing - principalRepayment) 0
let debtFinancingFraction = getParameter paramsDebtFinancingFraction
debtRetirementTime = getParameter paramsDebtRetirementTime
directCosts = production * variableProductionCost
discountRate = getParameter paramsDiscountRate
fractionalLossRate = getParameter paramsFractionalLossRate
grossIncome = billings
interestPayments = debt * interestRate
interestRate = getParameter paramsInterestRate
losses = accountsReceivable * fractionalLossRate
netCashFlow = cashReceipts + borrowing - newInvestment
- directCosts - interestPayments
- principalRepayment - taxes
netIncome = taxableIncome - taxes
newInvestment = ifDynamics (time .>=. buildingTime)
0 (requiredInvestment / buildingTime)
npvCashFlow <- npv netCashFlow discountRate 0 1
npvIncome <- npv netIncome discountRate 0 1
let price = getParameter paramsPrice
principalRepayment = debt / debtRetirementTime
productionCapacity = getParameter paramsProductionCapacity
requiredInvestment = getParameter paramsRequiredInvestment
taxes = taxableIncome * taxRate
variableProductionCost = getParameter paramsVariableProductionCost
experimentDataInStartTime
[(netIncomeName, seriesEntity "Net income" netIncome),
(netCashFlowName, seriesEntity "Net cash flow" netCashFlow),
(npvIncomeName, seriesEntity "NPV income" npvIncome),
(npvCashFlowName, seriesEntity "NPV cash flow" npvCashFlow)]
-- the names of the variables we are interested in
netIncomeName = "netIncome"
netCashFlowName = "netCashFlow"
npvIncomeName = "npvIncome"
npvCashFlowName = "npvCashFlow"
-- the simulation specs
specs = Specs 0 5 0.015625 RungeKutta4 SimpleGenerator
-- | The experiment for the Monte-Carlo simulation.
monteCarloExperiment :: Experiment
monteCarloExperiment =
defaultExperiment {
experimentSpecs = specs,
experimentRunCount = 1000,
experimentTitle = "Financial Model (the Monte-Carlo simulation)",
experimentDescription = "Financial Model (the Monte-Carlo simulation) as described in " ++
"Vensim 5 Modeling Guide, Chapter Financial Modeling and Risk.",
experimentGenerators =
[outputView defaultExperimentSpecsView,
outputView $ defaultDeviationChartView {
deviationChartTitle = "The deviation chart for Net Income and Cash Flow",
deviationChartSeries = [Left netIncomeName,
Left netCashFlowName] },
outputView $ defaultDeviationChartView {
deviationChartTitle = "The deviation chart for Net Present Value of Income and Cash Flow",
deviationChartSeries = [Left npvIncomeName,
Left npvCashFlowName] },
outputView $ defaultFinalHistogramView {
finalHistogramTitle = "Histogram for Net Income and Cash Flow",
finalHistogramSeries = [netIncomeName, netCashFlowName] },
outputView $ defaultFinalHistogramView {
finalHistogramTitle = "Histogram for Net Present Value of Income and Cash Flow",
finalHistogramSeries = [npvIncomeName, npvCashFlowName] },
outputView $ defaultFinalStatsView {
finalStatsTitle = "Summary for Net Income and Cash Flow",
finalStatsSeries = [netIncomeName, netCashFlowName] },
outputView $ defaultFinalStatsView {
finalStatsTitle = "Summary for Net Present Value of Income and Cash Flow",
finalStatsSeries = [npvIncomeName, npvCashFlowName] } ] }
-- | The experiment with single simulation run.
singleExperiment :: Experiment
singleExperiment =
defaultExperiment {
experimentSpecs = specs,
experimentTitle = "Financial Model",
experimentDescription = "Financial Model as described in " ++
"Vensim 5 Modeling Guide, Chapter Financial Modeling and Risk.",
experimentGenerators =
[outputView defaultExperimentSpecsView,
outputView $ defaultTimeSeriesView {
timeSeriesTitle = "Time series of Net Income and Cash Flow",
timeSeries = [Left netIncomeName,
Left netCashFlowName] },
outputView $ defaultTimeSeriesView {
timeSeriesTitle = "Time series of Net Present Value for Income and Cash Flow",
timeSeries = [Left npvIncomeName,
Left npvCashFlowName] },
outputView $ defaultTableView {
tableTitle = "Table",
tableSeries = [netIncomeName, netCashFlowName,
npvIncomeName, npvCashFlowName] } ] }
main = do
-- run the ordinary simulation
putStrLn "*** The simulation with default parameters..."
runExperiment singleExperiment $ model defaultParams
putStrLn ""
-- run the Monte-Carlo simulation
putStrLn "*** The Monte-Carlo simulation..."
randomParams >>= runExperimentParallel monteCarloExperiment . model