import System.Random import System.Environment import Control.Monad.Trans import Numeric minRate = 8.0 maxRate = 20.0 initialPrinciple = 50.0 periods = 36 data Loan = Loan {principle :: Double, rate :: Double, minPayment :: Double} -- Generate a random rate within the "rate window" getNewRate :: IO Double getNewRate = do randomRIO (minRate, maxRate) -- figure out what the minimum payment will be on a given loan calcMinimumPayment :: Double -> Double -> Double calcMinimumPayment p i = (r * p *(1+r)^periods) / ((1+r)^periods - 1) where r = i / 12.0 / 100 -- create a new loan newLoan :: Double -> IO Loan newLoan p = do i <- getNewRate let m = calcMinimumPayment p i return (Loan p i m) -- Given a loan, make a payment and create a new loan with the remaining principle calcPayment :: Loan -> Loan calcPayment l = if principle l > minPayment l then Loan (principle l - p) (rate l) (minPayment l) else Loan 0 (rate l) (principle l) -- mark this as the last payment where i = (principle l) * (rate l / 100 / 12) p = minPayment l - i -- Take the current account balance, and make as many loans as possible from it makeLoans :: Double -> IO [Loan] makeLoans bal = if bal >= initialPrinciple then do l <- newLoan initialPrinciple ls <- makeLoans (bal - initialPrinciple) return ([l] ++ ls) else return [] -- make payments on the given loans, and return the updated loans, and resulting total payments collectPayments :: [Loan] -> ([Loan], Double) collectPayments loans = (filteredLoans, payments) where clearStaleLoans = filter (\x -> minPayment x > 0) -- remove any loans that have been fully paid back filteredLoans = clearStaleLoans (map calcPayment loans) payments = sum (map minPayment filteredLoans) -- run through a loan scenario, reinvesting returns for 'term' months. Print out various statistics on the account run :: Double -> [Loan] -> Int -> Double -> IO Double run startingBalance loans term monthlyDeposit = if term <= 0 then return startingBalance else do l <- makeLoans startingBalance let (newLoans, newPayments) = collectPayments (loans ++ l) let newPrinciple = (initialPrinciple * fromIntegral (length l)) let newBalance = (startingBalance - newPrinciple + newPayments) let loanValue = sum (map principle newLoans) let averageRate = (sum (map rate newLoans)) / fromIntegral (length newLoans) putStr $ unlines ["Term: " ++ show term, "Loan count: " ++ show (length newLoans), "Average Rate: " ++ show averageRate, "Loan Value: " ++ show loanValue, "New balance: " ++ show newBalance, "New Principle: " ++ show newPrinciple, "New Payments: " ++ show newPayments,"---------"] bal <- (run (newBalance + monthlyDeposit) newLoans (term-1) monthlyDeposit) return bal main :: IO () main = do args <- getArgs let accountBalance = if(length args > 0) then read (args !! 0) :: Double else 300.0 let monthlyDeposit = if(length args > 1) then read (args !! 1) :: Double else 100.0 let term = if(length args > 2) then read (args !! 2) :: Int else 1 putStr $ unlines ["Starting balance: " ++ show accountBalance, "Starting run", "----------"] endingBalance <- run accountBalance [] (term * 12) monthlyDeposit putStr $ unlines ["Ending Balance: " ++ show endingBalance]