{-# 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 for the system of recursive diffential -- equations to provide the Sensitivity Analysis. -- -- 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. module Model (-- * Simulation Model model, -- * Variable Names netIncomeName, netCashFlowName, npvIncomeName, npvCashFlowName, -- * External Parameters Parameters(..), defaultParams, randomParams) where 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 Results 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 return $ results [resultSource netIncomeName "Net income" netIncome, resultSource netCashFlowName "Net cash flow" netCashFlow, resultSource npvIncomeName "NPV income" npvIncome, resultSource npvCashFlowName "NPV cash flow" npvCashFlow] -- the names of the variables we are interested in netIncomeName = "netIncome" netCashFlowName = "netCashFlow" npvIncomeName = "npvIncome" npvCashFlowName = "npvCashFlow"