{-# 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 -- from package aivika import Simulation.Aivika.Specs import Simulation.Aivika.Simulation import Simulation.Aivika.Dynamics import Simulation.Aivika.SystemDynamics import Simulation.Aivika.Parameter.Random -- from package aivika-experiment import Simulation.Aivika.Experiment import Simulation.Aivika.Experiment.ExperimentSpecsView import Simulation.Aivika.Experiment.TableView import Simulation.Aivika.Experiment.FinalStatsView -- from package aivika-experiment-chart import Simulation.Aivika.Experiment.DeviationChartView import Simulation.Aivika.Experiment.FinalHistogramView import Simulation.Aivika.Experiment.TimeSeriesView -- | The model parameters. data Parameters = Parameters { paramsTaxDepreciationTime :: Simulation Double, paramsTaxRate :: Simulation Double, paramsAveragePayableDelay :: Simulation Double, paramsBillingProcessingTime :: Simulation Double, paramsBuildingTime :: Simulation Double, paramsDebtFinancingFraction :: Simulation Double, paramsDebtRetirementTime :: Simulation Double, paramsDiscountRate :: Simulation Double, paramsFractionalLossRate :: Simulation Double, paramsInterestRate :: Simulation Double, paramsPrice :: Simulation Double, paramsProductionCapacity :: Simulation Double, paramsRequiredInvestment :: Simulation Double, paramsVariableProductionCost :: Simulation 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 <- newRandomParameter 0.07 0.11 billingProcessingTime <- newRandomParameter 0.03 0.05 buildingTime <- newRandomParameter 0.8 1.2 fractionalLossRate <- newRandomParameter 0.05 0.08 interestRate <- newRandomParameter 0.09 0.15 price <- newRandomParameter 0.9 1.2 productionCapacity <- newRandomParameter 2200 2600 requiredInvestment <- newRandomParameter 1800 2200 variableProductionCost <- newRandomParameter 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 liftParam :: (Parameters -> Simulation a) -> Dynamics a liftParam f = liftSimulation $ 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 = liftParam paramsTaxDepreciationTime taxRate = liftParam paramsTaxRate accountsReceivable <- integ (billings - cashReceipts - losses) (billings / (1 / averagePayableDelay + fractionalLossRate)) let averagePayableDelay = liftParam paramsAveragePayableDelay awaitingBilling <- integ (price * production - billings) (price * production * billingProcessingTime) let billingProcessingTime = liftParam paramsBillingProcessingTime billings = awaitingBilling / billingProcessingTime borrowing = newInvestment * debtFinancingFraction buildingTime = liftParam paramsBuildingTime cashReceipts = accountsReceivable / averagePayableDelay debt <- integ (borrowing - principalRepayment) 0 let debtFinancingFraction = liftParam paramsDebtFinancingFraction debtRetirementTime = liftParam paramsDebtRetirementTime directCosts = production * variableProductionCost discountRate = liftParam paramsDiscountRate fractionalLossRate = liftParam paramsFractionalLossRate grossIncome = billings interestPayments = debt * interestRate interestRate = liftParam 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 = liftParam paramsPrice principalRepayment = debt / debtRetirementTime productionCapacity = liftParam paramsProductionCapacity requiredInvestment = liftParam paramsRequiredInvestment taxes = taxableIncome * taxRate variableProductionCost = liftParam 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 -- | 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