{-# LANGUAGE TypeSynonymInstances , NoMonomorphismRestriction , FlexibleInstances #-} -- | This module provides a collection of accounting actions for -- different asset/account types implemented via the 'Asset' class. -- -- Use these actions to build more complex accounting actions for -- 'Entity's from "Accounting". -- -- They are implemented using the more basic combinators from -- "Bookkeeping". -- -- Many assets make use the 'InterestRate's defined below. module HAX.Assets where import HAX.Bookkeeping import Control.Monad.Reader import HAX.Common -- * The Asset class -- | An asset is anything that can be handled within an accounting -- action. class Asset a where handle :: a -> AccountingRW s () -- ^ derives the action corresponding to the asset's characteristics -- * Transactional Account with asymmetric interest rates. -- | This assets calculates interest according to its average positive -- and negative account balances over the last time period and debits -- it againt the 'InterestRate's 'iSource' accounts. -- -- Other names: Revolving credit, line of credit, Kontokorrent data TransactionalAccount = TransactionalAccount { taCredit :: InterestRate -- ^ credit interest for negative balances -- for one period (not neccessarily p.a.) , taDebit :: InterestRate -- ^ debit interest for positive balances -- for one period (not neccessarily p.a.) , taAcc :: AccountName -- ^ asset's account name , taPeriod :: ASpan -- ^ period in months } instance Asset TransactionalAccount where handle (TransactionalAccount icredit idebit acc period) = do let avg m = sum m / fromIntegral (sMonths period) tx a b c = interestTx a (b ++ " für "++ show acc) acc (Just $ return $ avg c) date <- curDate onceEvery period (month 12) $ do (debit,credit) <- partition (>0) <$> balancesSince (shift (1-period) date) acc when ((sum $ debit ++ fmap negate credit) /= 0) $ logLedger $ printf "%s: positive balances: %v, negative balances: %v" acc (PList debit) (PList credit) tx icredit "Sollzins" credit tx idebit "Habenzins" debit return () -- * Loan with fixed (annuity) or variable payments -- | Select a payment schedule data PaymentType = Linear Decimal -- ^ Decreasing payments with fixed repay and -- decreasing interest portions. -- -- The number specifies the repay as a fraction of -- the principal amount. | Annuity Decimal -- ^ Fixed payments with decreasing interest and -- increasing repay portions. -- -- The number specifies the initial repay as a -- fraction of the principal amount. deriving Show data Loan = Loan { lPrincipal :: Amount -- ^ Principal amount payed to lPaymentAccount , lPType :: PaymentType , lPaymentAcc :: AccountName -- ^ account for the payments , lLoanAcc :: AccountName -- ^ account for the open balance , lInterest :: InterestRate -- ^ interest rate for one period (not neccessarily p.a.) , lStart :: ADate -- ^ payout date , lPeriod :: ASpan -- ^ period of the interest payments } instance Asset Loan where handle (Loan principal pType paymentAcc loanAcc ir start period) = do date <- curDate let name = show loanAcc dur = duration pType ir onceAt start $ fromTo principal ("Principal for "++name) loanAcc paymentAcc >> lift (do printf "Loan %v runs for %v periods\n" name $ show $ roundTo 3 $ conv dur -- printf "%s == %s" (show pType) $ show $ annuity dur (iRate ir) - (iRate ir) ) onlyAfter start $ onceEvery period start $ do curInterest <- interestTx ir ("Interest on "++name) loanAcc Nothing let payment (Linear x) = x*principal + curInterest payment (Annuity x)= x*principal + interest ir principal fromToLimit (negate $ roundTo 3 $ payment pType) ("Rate for "++name) loanAcc paymentAcc -- * Liquitidy Simulation -- | This 'Asset' produces a list of twelve payments, one for each -- month, that are shifted in such a way, that they sum up to zero data Liquidity = Liquidity { lFrom :: AccountName , lTo :: AccountName , lPayments :: [Amount] } instance Asset Liquidity where handle (Liquidity from to pays) = do m <-reader (getMonth.eDate) fromTo (pays !! pred m) "Liquidity Simulation" from to where avg = sum (take 12 pays) / 12 -- ** Helpers -- | Calculate the number of periods time until the loan is completely repaid. duration :: PaymentType -> InterestRate -> Decimal duration ptype ir = conv $ duration' ptype $ iRate ir where duration' (Linear x) _ = conv $ 1/x duration' (Annuity x) 0 = conv $ 1/x duration' (Annuity x) rate = negate $ on logBase conv (1 + rate) (x/(rate + x)) -- | Calculate the annuity (as a fraction of the principal) for a -- given number of periods interest rate. annuity :: Decimal -- ^ number of periods -> Decimal -- ^ interest rate -> Amount annuity duration' rate' = conv $ ((1 + rate) ** duration) * rate/(((1 + rate) ** duration) - 1) where rate = conv rate' :: Double duration = conv duration' :: Double -- * Fixed Payments -- | Recurring Fixed Payment data FixedPayment = FixedPayment { fPayment :: Amount , fStart :: ADate , fOffset :: ADate , fPeriod :: ASpan , fSource :: AccountName , fSink :: AccountName , fComment :: Comment } instance Asset FixedPayment where handle (FixedPayment am start offset period source sink comment) = onlyAfter start $ onceEvery period offset $ fromTo am comment source sink -- * Interest Rates -- | Interest rates together with the nominal account, the could be -- identified with the source (for incoming interest payments) or sink -- (for outgoing, i.e. negative interest payments). -- -- For example, if the interest is an expense, 'iSource' could be -- \"Expenses\". If the interest is capital yield, 'iSource' could be -- \"Income\". data InterestRate = IR { iRate :: Amount , iSource :: AccountName -- ^ this can be something like \"Earnings\" if the interest can -- be considered earnings or another entity, if } -- | Calculate the interest for a given amount interest :: InterestRate -> Amount -> Amount interest ir am = roundTo 2 $ am * (iRate ir) -- | Calculate the interest for a current balance of an account currentInterest :: (Monoid w, Ledger l) => InterestRate-> AccountName -> AmountA s l w currentInterest ir acc = interest ir <$> currentBalance acc -- | Calculate and transfer the interest from the 'iSource' account to -- some other account. interestTx :: InterestRate -> String -- ^ comment -> AccountName -- ^ Sink account -> Maybe (AmountRW s) -- ^ optional: Use this amount instead of the balance of -- the sink account -> AmountRW s interestTx ir comment acc maybeAm = do am <- maybe (currentInterest ir acc) (interest ir <$>) maybeAm when (am /= 0) $ fromTo am comment (iSource ir) acc return am