packages feed

Hastructure-0.45.0: src/Liability.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE InstanceSigs #-}

module Liability
  (Bond(..),BondType(..),OriginalInfo(..)
  ,payInt,payPrin,consolStmt,isPaidOff,getCurBalance
  ,priceBond,pv,InterestInfo(..),RateReset(..)
  ,getDueInt,weightAverageBalance,calcZspread,payYield,getTotalDueInt
  ,buildRateResetDates,isAdjustble,StepUp(..),isStepUp,getDayCountFromInfo
  ,calcWalBond,patchBondFactor,fundWith,writeOff,InterestOverInterestType(..)
  ,getCurBalance,setBondOrigDate
  ,bndOriginInfoLens,bndIntLens,getBeginRate,_Bond,_BondGroup
  ,totalFundedBalance,getIndexFromInfo,buildStepUpDates
  ,accrueInt,stepUpInterestInfo,payIntByIndex,_MultiIntBond
  ,getDueIntAt,getDueIntOverIntAt,getDueIntOverInt,getTotalDueIntAt
  ,getCurRate,bondCashflow,getOutstandingAmount,valueBond,getTxnRate
  ,getAccrueBegDate,getTxnInt,adjInterestInfoByRate,adjInterestInfoBySpread
  ,interestInfoTraversal,getOriginBalance,curRatesTraversal
  ,backoutAccruedInt,extractIrrResult,adjustBalance
  )
  where

import Data.Aeson       hiding (json)
import Data.Aeson.TH
import Data.Fixed
import qualified Data.Time as T
import Lib (Period(..),Ts(..) ,TsPoint(..) ,daysBetween, weightedBy,paySeqLiabResi)
import Util
import DateUtil
import Types
import Analytics
import Data.Ratio 
import Data.Maybe
import Data.List
import qualified Data.Set as Set
import qualified Data.DList as DL
import qualified Stmt as S 
import qualified Cashflow as CF
import qualified InterestRate as IR
import qualified Lib
import GHC.Generics
import qualified Data.Map as Map
import Debug.Trace
import InterestRate (UseRate(getIndexes))
import Language.Haskell.TH
import Control.Lens hiding (Index)
import Control.Lens.TH
import Language.Haskell.TH.Lens 
import Stmt (getTxnAmt)
import Numeric.RootFinding


debug = flip trace

-- | test if a bond may changes its interest rate
isAdjustble :: InterestInfo -> Bool 
isAdjustble Floater {} = True
isAdjustble RefRate {} = True
isAdjustble Fix {} = False
isAdjustble (CapRate r _ ) = isAdjustble r
isAdjustble (FloorRate r _ ) = isAdjustble r
isAdjustble (WithIoI r _) = isAdjustble r
isAdjustble (RefBal _ r) = isAdjustble r


isStepUp :: Bond -> Bool
isStepUp Bond{bndStepUp = Nothing} = False
isStepUp _  = True

getIndexFromInfo :: InterestInfo -> Maybe [Index]
getIndexFromInfo (Floater _ idx _ _  _ _ _) = Just [idx]
getIndexFromInfo Fix {} = Nothing 
getIndexFromInfo RefRate {} = Nothing 
getIndexFromInfo (CapRate info _) = getIndexFromInfo info
getIndexFromInfo (FloorRate info _) = getIndexFromInfo info
getIndexFromInfo (WithIoI info _) = getIndexFromInfo info
getIndexFromInfo (RefBal _ info) = getIndexFromInfo info

getDayCountFromInfo :: InterestInfo -> Maybe DayCount
getDayCountFromInfo (Floater _ _ _ _ dc _ _) = Just dc
getDayCountFromInfo (Fix _ dc) = Just dc
getDayCountFromInfo RefRate {} = Nothing 
getDayCountFromInfo (RefBal ds info) = getDayCountFromInfo info
getDayCountFromInfo (CapRate info _) = getDayCountFromInfo info
getDayCountFromInfo (FloorRate info _) = getDayCountFromInfo info
getDayCountFromInfo (WithIoI info _) = getDayCountFromInfo info
getDayCountFromInfo _ = Nothing

type RateReset = DatePattern 

data InterestOverInterestType = OverCurrRateBy Rational -- ^ inflat ioi rate by pct over current rate
                              | OverFixSpread Spread -- ^ inflat ioi rate by fix spread
                              deriving (Show, Eq, Generic, Ord, Read)


-- ^ the way how interest due amount is calculated
--------------------------- start Rate, index, spread, reset dates, daycount, floor, cap
data InterestInfo = Floater IRate Index Spread RateReset DayCount (Maybe Floor) (Maybe Cap)
                  | Fix IRate DayCount                                    -- ^ fixed rate
                  | RefBal DealStats InterestInfo                         -- ^ accure interest based on balance(described by a formula)
                  | RefRate IRate DealStats Float RateReset               -- ^ interest rate depends to a formula
                  | CapRate InterestInfo IRate                            -- ^ cap rate 
                  | FloorRate InterestInfo IRate                          -- ^ floor rate
                  | WithIoI InterestInfo InterestOverInterestType         -- ^ Interest Over Interest(normal on left,IoI on right)
                  deriving (Show, Eq, Generic, Ord, Read)

-- ^ scale a spread to interest rate info
adjInterestInfoByRate :: Rate -> InterestInfo -> InterestInfo
adjInterestInfoByRate r (Floater a idx s dp dc f c) = Floater (a* fromRational r) idx (s* fromRational r) dp dc f c
adjInterestInfoByRate r (Fix a dc) = Fix (a* fromRational r) dc
adjInterestInfoByRate r (RefRate a ds f dp) = RefRate (a* fromRational r) ds (f* fromRational r) dp
adjInterestInfoByRate r (RefBal ds ii) = RefBal ds (adjInterestInfoByRate r ii)
adjInterestInfoByRate r (CapRate ii a) = CapRate (adjInterestInfoByRate r ii) a
adjInterestInfoByRate r (FloorRate ii a) = FloorRate (adjInterestInfoByRate r ii) a
adjInterestInfoByRate r (WithIoI ii ooi) = WithIoI (adjInterestInfoByRate r ii) ooi

-- ^ add a spread to interest rate info
adjInterestInfoBySpread :: Spread -> InterestInfo -> InterestInfo
adjInterestInfoBySpread s (Floater a idx s' dp dc f c) = Floater s idx (s+s') dp dc f c
adjInterestInfoBySpread s (Fix a dc) = Fix (a+s) dc
adjInterestInfoBySpread s (RefRate a ds f dp) = RefRate (a+s) ds f dp
adjInterestInfoBySpread s (RefBal ds ii) = RefBal ds (adjInterestInfoBySpread s ii)
adjInterestInfoBySpread s (CapRate ii a) = CapRate (adjInterestInfoBySpread s ii) a
adjInterestInfoBySpread s (FloorRate ii a) = FloorRate (adjInterestInfoBySpread s ii) a
adjInterestInfoBySpread s (WithIoI ii ooi) = WithIoI (adjInterestInfoBySpread s ii) ooi


stepUpInterestInfo :: StepUp -> InterestInfo -> InterestInfo
stepUpInterestInfo sp ii =
  case ii of 
    (Floater a idx s dp dc f c) -> Floater a idx (s+getSpread sp) dp dc f c
    (Fix r dc) -> Fix (r+getSpread sp) dc
    (CapRate ii' r) -> CapRate (stepUpInterestInfo sp ii') r
    (FloorRate ii' r) -> FloorRate (stepUpInterestInfo sp ii') r
    (WithIoI ii' ooi) -> WithIoI (stepUpInterestInfo sp ii') ooi
    (RefBal ds ii') -> RefBal ds (stepUpInterestInfo sp ii')
    _ -> ii
  where
    getSpread (PassDateSpread _ s) = s
    getSpread (PassDateLadderSpread _ s _) = s


-- ^ get reset dates from interest info
getDpFromIntInfo :: InterestInfo -> Maybe DatePattern
getDpFromIntInfo (Floater _ _ _ dp _ _ _) = Just dp
getDpFromIntInfo (RefRate _ _ _ dp) = Just dp
getDpFromIntInfo (RefBal _ ii) = getDpFromIntInfo ii
getDpFromIntInfo (CapRate ii _) = getDpFromIntInfo ii
getDpFromIntInfo (FloorRate ii _) = getDpFromIntInfo ii
getDpFromIntInfo (WithIoI ii _) = getDpFromIntInfo ii
getDpFromIntInfo _ = Nothing


getBeginRate :: InterestInfo -> IRate 
getBeginRate (Floater a _ _ _ _ _ _ ) = a
getBeginRate (Fix a _ ) = a
getBeginRate (RefRate a _ _ _ ) = a
getBeginRate (CapRate a  _ ) = getBeginRate a
getBeginRate (FloorRate a  _ ) = getBeginRate a
getBeginRate (WithIoI a _) = getBeginRate a
getBeginRate (RefBal _ a) = getBeginRate a


data StepUp = PassDateSpread Date Spread                   -- ^ add a spread on a date and effective afterwards
            | PassDateLadderSpread Date Spread RateReset   -- ^ add a spread on the date pattern
            deriving (Show, Eq, Generic, Ord, Read)


data OriginalInfo = OriginalInfo {
  originBalance::Balance           -- ^ issuance balance
  ,originDate::Date                -- ^ issuance date
  ,originRate::Rate                -- ^ issuance rate of the bond
  ,maturityDate :: Maybe Date      -- ^ optional maturity date
} deriving (Show, Eq, Generic, Ord, Read)


type PlannedAmorSchedule = Ts
-- ^ the way of principal due is calculated
data BondType = Sequential                                 -- ^ Pass through type tranche
              | PAC PlannedAmorSchedule                    -- ^ bond with schedule amortization 
              | AmtByPeriod (PerCurve Balance)             -- ^ principal due by period
              | PacAnchor PlannedAmorSchedule [BondName]   -- ^ pay till schdule balance if bonds from bond names has oustanding balance, if other bonds are paid off ,then pay oustanding balance
              | Lockout Date                               -- ^ No principal due till date
              | IO
              | Z                                          -- ^ Z tranche
              | Equity                                     -- ^ Equity type tranche
              deriving (Show, Eq, Generic, Ord, Read)


-- TODO: for multi int bond, should origin rate be a list of rates?
--     : so far remain orginate rate as a single rate for multi int bond
data Bond = Bond {
              bndName :: String
              ,bndType :: BondType                 -- ^ bond type ,which describe the how principal due was calculated
              ,bndOriginInfo :: OriginalInfo       -- ^ fact data on origination
              ,bndInterestInfo :: InterestInfo     -- ^ interest info which used to update interest rate
              ,bndStepUp :: Maybe StepUp           -- ^ step up which update interest rate
              -- status
              ,bndBalance :: Balance               -- ^ current balance
              ,bndRate :: IRate                    -- ^ current rate
              ,bndDuePrin :: Balance               -- ^ principal due for current period
              ,bndDueInt :: Balance                -- ^ interest due
              ,bndDueIntOverInt :: Balance         -- ^ IoI
              ,bndDueIntDate :: Maybe Date         -- ^ last interest due calc date
              ,bndLastIntPay :: Maybe Date         -- ^ last interest pay date
              ,bndLastPrinPay :: Maybe Date        -- ^ last principal pay date
              ,bndStmt :: Maybe S.Statement        -- ^ transaction history
            } 
            | MultiIntBond {
              bndName :: String
              ,bndType :: BondType                    -- ^ bond type ,which describe the how principal due was calculated
              ,bndOriginInfo :: OriginalInfo          -- ^ fact data on origination
              ,bndInterestInfos :: [InterestInfo]     -- ^ interest info which used to update interest rate
              ,bndStepUps :: Maybe [StepUp]           -- ^ step up which update interest rate
              -- status
              ,bndBalance :: Balance                  -- ^ current balance
              ,bndRates :: [IRate]                    -- ^ current rate
              ,bndDuePrin :: Balance                  -- ^ principal due for current period
              ,bndDueInts :: [Balance]                -- ^ interest due
              ,bndDueIntOverInts :: [Balance]         -- ^ IoI
              ,bndDueIntDate :: Maybe Date            -- ^ last interest due calc date
              ,bndLastIntPays :: Maybe [Date]         -- ^ last interest pay date
              ,bndLastPrinPay :: Maybe Date           -- ^ last principal pay date
              ,bndStmt :: Maybe S.Statement           -- ^ transaction history
            }
            | BondGroup (Map.Map String Bond) (Maybe BondType)      -- ^ bond group
            deriving (Show, Eq, Generic, Ord, Read)            

interestInfoTraversal :: Traversal' Bond InterestInfo
interestInfoTraversal f (Bond bn bt oi ii su bal r dp di dioi did lip lpp stmt) 
  = (\ii' -> Bond bn bt oi ii' su bal r dp di dioi did lip lpp stmt) <$> f ii
interestInfoTraversal f (MultiIntBond bn bt oi iis sus bal rs dp dis diois did lips lpp stmt)
  = (\iis' -> MultiIntBond bn bt oi iis' sus bal rs dp dis diois did lips lpp stmt) <$> traverse f iis
interestInfoTraversal f (BondGroup bMap x) 
  = BondGroup <$> traverse (interestInfoTraversal f) bMap <*> pure x

curRatesTraversal :: Traversal' Bond IRate
curRatesTraversal f (Bond bn bt oi ii su bal r dp di dioi did lip lpp stmt) 
  = (\r' -> Bond bn bt oi ii su bal r' dp di dioi did lip lpp stmt) <$> f r
curRatesTraversal f (MultiIntBond bn bt oi iis sus bal rs dp dis diois did lips lpp stmt)
  = (\rs' -> MultiIntBond bn bt oi iis sus bal rs' dp dis diois did lips lpp stmt) <$> traverse f rs
curRatesTraversal f (BondGroup bMap x)
  = BondGroup <$> traverse (curRatesTraversal f) bMap <*> pure x

adjustBalance :: Balance -> Bond -> Bond
adjustBalance bal b@Bond{bndBalance = _, bndOriginInfo = oi } 
  = b {bndBalance = bal, bndOriginInfo = oi {originBalance = bal}}

bndmStmt :: Lens' Bond (Maybe S.Statement)
bndmStmt = lens getter setter
  where 
    getter Bond{bndStmt = mStmt} = mStmt
    getter MultiIntBond{bndStmt = mStmt} = mStmt
    -- getter BondGroup{bndStmt = mStmt} = mStmt
    setter Bond{bndStmt = _} mStmt = Bond{bndStmt = mStmt}
    setter MultiIntBond{bndStmt = _} mStmt = MultiIntBond{bndStmt = mStmt}
    -- setter BondGroup{bndStmt = _} mStmt = BondGroup{bndStmt = mStmt}

bondCashflow :: Bond -> ([Date], [Amount])
bondCashflow b = 
  let t = S.getAllTxns b
  in 
    (S.getDate <$> t, S.getTxnAmt <$> t)

-- ^ remove empty transaction frgetBondByName :: Ast.Assetom a bond
consolStmt :: Bond -> Bond
consolStmt (BondGroup bMap x) = BondGroup (consolStmt <$> bMap) x
consolStmt b
  | S.hasEmptyTxn b = b
  | otherwise = let 
                  txn:txns = S.getAllTxns b
                  combinedBondTxns = foldl S.consolTxn [txn] txns    
                  droppedTxns = dropWhile S.isEmptyTxn combinedBondTxns 
                in 
                  b {bndStmt = Just (S.Statement (DL.fromList (reverse droppedTxns)))}

setBondOrigDate :: Date -> Bond -> Bond
setBondOrigDate d b@Bond{bndOriginInfo = oi} = b {bndOriginInfo = oi{originDate = d}}
setBondOrigDate d b@MultiIntBond{bndOriginInfo = oi} = b {bndOriginInfo = oi{originDate = d}}
setBondOrigDate d (BondGroup bMap x) = BondGroup ((setBondOrigDate d) <$> bMap) $ x

-- ^ build bond factors
patchBondFactor :: Bond -> Bond
patchBondFactor (BondGroup bMap x) = BondGroup (patchBondFactor <$> bMap) $ x
patchBondFactor bnd
  | S.hasEmptyTxn bnd = bnd
  | (originBalance (bndOriginInfo bnd)) == 0 = bnd
  | otherwise = let 
                  oBal = originBalance (bndOriginInfo bnd)
                  toFactor (BondTxn d b i p r0 c e f Nothing t) = (BondTxn d b i p r0 c e f (Just (fromRational (divideBB b oBal))) t)
                  -- newStmt = S.Statement $ toFactor <$> (S.getAllTxns bnd)
                  newBnd = case bndStmt bnd of 
                              Nothing -> bnd 
                              Just (S.Statement txns) -> bnd {bndStmt = Just (S.Statement (toFactor <$> txns)) }
                in 
                  newBnd

payInt :: Date -> Amount -> Bond -> Bond
-- pay 0 interest, do nothing
payInt d 0 b = b

-- pay interest
payInt d amt bnd@(Bond bn bt oi iinfo _ bal r duePrin dueInt dueIoI dueIntDate lpayInt lpayPrin stmt)
  = bnd {bndDueInt=newDue, bndStmt=newStmt, bndLastIntPay = Just d, bndDueIntOverInt = newDueIoI}
  where
    rs = Lib.paySeqLiabilitiesAmt amt [dueIoI, dueInt] -- `debug` ("date"++ show d++"due "++show dueIoI++">>"++show dueInt)
    newDueIoI = dueIoI - head rs
    newDue = dueInt - rs !! 1 -- `debug` ("Avail fund"++ show amt ++" int paid out plan"++ show rs)
    newStmt = case bt of 
                Equity -> S.appendStmt (BondTxn d bal amt 0 r amt newDue newDueIoI Nothing (S.PayYield bn)) stmt 
                _ -> S.appendStmt (BondTxn d bal amt 0 r amt newDue newDueIoI Nothing (S.PayInt [bn])) stmt  -- `debug` ("date after"++ show d++"due "++show newDueIoI++">>"++show newDue)

-- pay multi-int bond ,IOI first and then interest due, sequentially
payInt d amt bnd@(MultiIntBond bn bt oi iinfo _ bal rs duePrin dueInts dueIoIs dueIntDate lpayInt lpayPrin stmt)
  = bnd {bndDueInts=newDues, bndStmt=newStmt
        , bndLastIntPays = Just (replicate l d), bndDueIntOverInts = newDueIoIs}
  where
    l = length iinfo
    ioiPaid = Lib.paySeqLiabilitiesAmt amt dueIoIs
    afterIoI = amt - sum ioiPaid
    duePaid = Lib.paySeqLiabilitiesAmt afterIoI dueInts
    newDueIoIs = zipWith (-) dueIoIs ioiPaid
    newDues = zipWith (-) dueInts duePaid
    newDueIoI = sum newDueIoIs
    newDue = sum newDues
    newStmt = case bt of 
                Equity -> S.appendStmt (BondTxn d bal amt 0 (sum rs) amt newDue newDueIoI Nothing (S.PayYield bn)) stmt 
                _ -> S.appendStmt (BondTxn d bal amt 0 (sum rs) amt newDue newDueIoI Nothing (S.PayInt [bn])) stmt  -- `debug` ("date after"++ show d++"due "++show newDueIoI++">>"++show newDue)

payIntByIndex :: Date -> Int -> Amount -> Bond -> Bond
-- pay 0 interest, do nothing
payIntByIndex d _ 0 b = b
payIntByIndex d idx amt bnd@(MultiIntBond bn bt oi iinfo _ bal rs duePrin dueInts dueIoIs dueIntDate lpayInt lpayPrin stmt) 
  = let
      dueIoI = dueIoIs !! idx 
      dueInt = dueInts !! idx -- `debug` ("date"++ show d++"in pay index fun"++ show amt)
      [newDueIoI,newDue] = Lib.paySeqLiabResi amt [dueIoI, dueInt] -- `debug` ("date"++ show d++" before pay due "++show dueIoI++">>"++show dueInt)
      newStmt = S.appendStmt (BondTxn d bal amt 0 (sum rs) amt newDue newDueIoI Nothing (S.PayInt [bn])) stmt -- `debug` ("date after"++ show d++"due(ioi) "++show newDueIoI++">> due "++show newDue)
      od = getOriginDate bnd
      ods = replicate (length iinfo) od
    in 
      bnd {bndDueInts = dueInts & ix idx .~ newDue
          ,bndDueIntOverInts = dueIoIs & ix idx .~ newDueIoI
          ,bndStmt = newStmt
          ,bndLastIntPays = case lpayInt of 
                              Nothing -> Just $ ods & ix idx .~ d
                              Just ds -> Just $ ds & ix idx .~ d}


-- ^ pay interest to single bond regardless any interest due
payYield :: Date -> Amount -> Bond -> Bond 
payYield d amt bnd@(Bond bn bt oi iinfo _ bal r duePrin dueInt dueIoI dueIntDate lpayInt lpayPrin stmt)
  = bnd {bndDueInt = newDue,bndDueIntOverInt=newDueIoI, bndStmt= newStmt}
  where
    [newDue,newDueIoI] = paySeqLiabResi amt [dueIoI, dueInt]
    newStmt = S.appendStmt (BondTxn d bal amt 0 r amt newDue newDueIoI Nothing (S.PayYield bn)) stmt 


-- ^ pay principal to single bond principal with limit of principal due
payPrin :: Date -> Amount -> Bond -> Bond
-- ^ no cash payment , do nothing
payPrin d 0 bnd = bnd
-- ^ no oustanding balance , do nothing
payPrin d _ bnd@(Bond bn bt oi iinfo _ 0 r 0 0 dueIoI dueIntDate lpayInt lpayPrin stmt) = bnd

payPrin d amt bnd = bnd {bndDuePrin =newDue, bndBalance = newBal , bndStmt=newStmt} 
  where
    newBal = (bndBalance bnd) - amt
    newDue = (bndDuePrin bnd) - amt 
    bn = bndName bnd
    stmt = bndStmt bnd
    dueIoI = getDueIntOverInt bnd
    dueInt = getDueInt bnd
    r = getCurRate bnd
    newStmt = S.appendStmt (BondTxn d newBal 0 amt r amt dueInt dueIoI Nothing (S.PayPrin [bn] )) stmt 


writeOff :: Date -> Amount -> Bond -> Either String Bond
writeOff d 0 b = Right b
writeOff d amt _bnd 
  | bndBalance _bnd < amt = Left $ "Insufficient balance to write off "++ show amt ++ show " bond name "++ show (bndName _bnd)
  | otherwise = 
    let 
      bnd = accrueInt d _bnd
      newBal = bndBalance bnd - amt
      dueIoI = getDueIntOverInt bnd
      dueInt = getDueInt bnd
      bn = bndName bnd
      stmt = bndStmt bnd
      newStmt = S.appendStmt (BondTxn d newBal 0 0 0 0 dueInt dueIoI Nothing (S.WriteOff bn amt )) stmt 
    in 
      Right $ bnd {bndBalance = newBal , bndStmt=newStmt}

-- TODO: should increase the original balance of the bond?
fundWith :: Date -> Amount -> Bond -> Bond
fundWith d 0 b = b
fundWith d amt _bnd = bnd {bndBalance = newBal, bndStmt=newStmt } 
  where
    bnd = accrueInt d _bnd
    dueIoI = getDueIntOverInt bnd
    dueInt = getDueInt bnd
    bn = bndName bnd
    stmt = bndStmt bnd
    newBal = bndBalance bnd + amt
    newStmt = S.appendStmt (BondTxn d newBal 0 (negate amt) 0 0 dueInt dueIoI Nothing (S.FundWith bn amt )) stmt 


-- ^ get interest rate for due interest
getIoI :: InterestInfo -> IRate -> IRate
-- ^ inflate interest rate by pct over current rate
getIoI (WithIoI _ (OverCurrRateBy r)) rate = rate * (1+ fromRational r)
-- ^ inflate interest rate by adding a fix spread
getIoI (WithIoI _ (OverFixSpread r)) rate = rate + r
-- ^ no inflation,just use current bond's rate
getIoI _ rate = rate

-- ^ accure interest to a bond, update the due interest and due IoI of the bond
accrueInt :: Date -> Bond -> Bond
accrueInt d b@Bond{bndInterestInfo = ii,bndDueIntDate = mDueIntDate, bndDueInt= dueInt
                  , bndDueIntOverInt = dueIoI, bndRate = r, bndBalance = bal} 
  | d == beginDate = b
  | otherwise = let 
                  dc = (fromMaybe DC_ACT_365F) (getDayCountFromInfo ii)
                  r2 = getIoI ii r
                  period = yearCountFraction dc beginDate d
                  -- newDue = mulBR bal $ toRational r * period 
                  newDue = IR.calcInt bal beginDate d r dc
                  newIoiDue = mulBR dueInt (toRational r2 * period)
                in 
                  b {bndDueInt = newDue+dueInt, bndDueIntOverInt = dueIoI+newIoiDue
                    ,bndDueIntDate = Just d}
  where
    beginDate = case mDueIntDate of
                  Just _d -> _d
                  Nothing -> getOriginDate b


-- accure all the index 
accrueInt d b@MultiIntBond{bndInterestInfos = iis, bndDueIntDate = mDueIntDate 
                            , bndDueInts = dueInts, bndDueIntOverInts = dueIoIs
                            , bndRates = rs, bndBalance = bal}
  | beginDate == d = b
  | otherwise 
      = let 
        l = length iis -- `debug` ("bond Name>>> "++ show (bndName b))
        daycounts = (fromMaybe DC_ACT_365F) . getDayCountFromInfo <$> iis
        periods = zipWith3 yearCountFraction daycounts (replicate l beginDate) (repeat d) -- `debug` ((bndName b) ++"  date"++ show d++"daycounts"++show daycounts++"beginDates "++show beginDates++ show "end dates"++ show d)
        newDues = zipWith3 (\r p due -> (mulBR (mulBIR bal r) p) + due) rs periods dueInts -- `debug` ((bndName b) ++"  date"++ show d++"rs"++show rs++"periods "++show periods++">>"++show dueInts)
        newIoiDues = zipWith5 (\r p due dueIoI ii -> 
                                (mulBR (mulBIR due (getIoI ii r)) p) + dueIoI)
                              rs
                              periods 
                              dueInts
                              dueIoIs
                              iis
      in
        b {bndDueInts = newDues, bndDueIntOverInts = newIoiDues, bndDueIntDate = Just d }
    where 
      l = length iis
      beginDate = case mDueIntDate of
                    Just ds -> ds
                    Nothing -> getOriginDate b

accrueInt d (BondGroup bMap x) = BondGroup (accrueInt d <$> bMap) $ x


calcWalBond :: Date -> Bond -> Rational
calcWalBond d b@Bond{bndStmt = Nothing} = 0.0
calcWalBond d b@MultiIntBond{bndStmt = Nothing} = 0.0
calcWalBond d (BondGroup bMap _) 
  = let
      bndWal = calcWalBond d <$> Map.elems bMap 
      bndBals = toRational . getCurBalance <$> Map.elems bMap
    in 
      weightedBy bndBals bndWal

calcWalBond d b
  = let 
      txns = cutBy Exc Future d $ S.getAllTxns b 
      cutoffBalance =  (S.getTxnBegBalance . head ) txns 
      lastBalance = (S.getTxnBalance . last) txns 
      firstTxnDate = d 
      gapDays = (daysBetween firstTxnDate) . S.getDate <$> txns
      weightPrins = zipWith (*) (S.getTxnPrincipal <$> txns) (fromIntegral <$> gapDays) 
      wal = sum weightPrins / 365 / cutoffBalance 
    in 
      if lastBalance > 0 then
        0  
      else
        toRational wal -- `debug` ("WAL-->"++show (bndName b)++">>"++show wal)


getTxnRate :: Txn -> IRate
getTxnRate (BondTxn _ _ _ _ r _ _ _ _ _) = r
getTxnRate _ = 0.0

getTxnInt :: Txn -> Amount
getTxnInt (BondTxn _ _ _ i _ _ _ _ _ _) = i
getTxnInt _ = 0.0


-- ^ get present value of a bond
priceBond :: Date -> Ts -> Bond -> PriceResult
priceBond d rc b@(Bond _ _ _ _ _ _ _ _ _ _ _ _ _ Nothing ) = PriceResult 0 0 0 0 0 0 []
priceBond d rc b@(MultiIntBond _ _ _ _ _ _ _ _ _ _ _ _ _ Nothing ) = PriceResult 0 0 0 0 0 0 []
priceBond d rc bnd
  | all (==0) (S.getTxnAmt <$> futureCfs) = PriceResult 0 0 0 0 0 0 []
  | otherwise 
      = let
          presentValue = pv3 rc d (getDate <$> futureCfs) (getTxnAmt <$> futureCfs)
          cutoffBalance = case S.getTxnAsOf txns d of
                              Nothing ->  (S.getTxnBegBalance . head) txns
                              Just _txn -> S.getTxnBegBalance _txn
          -- TODO: what if in current deal,no transaction before pricing day ? what's the begin day for interest to accrual?
          accruedInt = backoutAccruedInt d (getOriginDate bnd) txns

          wal = calcWalBond d bnd
          duration = calcDuration DC_ACT_365F d (zip futureCfDates futureCfFlow) rc
          convexity = calcConvexity DC_ACT_365F d (zip futureCfDates futureCfFlow) rc
        in 
          PriceResult presentValue (fromRational (100* (safeDivide' presentValue obal))) (realToFrac wal) (realToFrac duration) (realToFrac convexity) accruedInt futureCfs -- `debug` ("Acc int"++ show accruedInt )
  where 
    cr = getCurRate bnd
    bal = getCurBalance bnd
    txns = S.getAllTxns bnd
    futureCfs = cutBy Exc Future d txns
    futureCfDates = getDate <$> futureCfs
    futureCfFlow = getTxnAmt <$> futureCfs
    obal = getOriginBalance bnd
    od = getOriginDate bnd

valueBond :: BondPricingMethod -> Date -> [(Date,Balance)] -> Balance
valueBond _ _ [] = 0

extractIrrResult :: PriceResult -> Maybe IRR
extractIrrResult priceResult = fst <$> preview _IrrResult priceResult

backoutAccruedInt :: Date -> Date -> [Txn] -> Amount
backoutAccruedInt d txnStartDate txns =
  case splitByDate txns d EqToLeft of 
    (lastTxns, []) -> 0
    ([], x:xs) -> IR.calcInt (S.getTxnBegBalance x) txnStartDate d (getTxnRate x) DC_ACT_365F  -- `debug` ("backout Acc 0 "++ show (S.getTxnBegBalance x)++" "++ show txnStartDate++" "++ show d++" "++ show (getTxnRate x))
    (lastTxns, x:xs) -> IR.calcInt (S.getTxnBegBalance x) (getDate (last lastTxns)) d (getTxnRate x) DC_ACT_365F -- `debug` ("backout Acc 1"++ show (S.getTxnBegBalance x)++" "++ show (getDate (last lastTxns))++" "++ show d++" "++ show (getTxnRate x))

weightAverageBalance :: Date -> Date -> Bond -> Balance
weightAverageBalance sd ed b@(Bond _ _ (OriginalInfo ob bd _ _ )  _ _ currentBalance _ _ _ _ _ _ _ Nothing) 
  = mulBR currentBalance (yearCountFraction DC_ACT_365F (max bd sd) ed) 
weightAverageBalance sd ed b@(MultiIntBond _ _ (OriginalInfo ob bd _ _ )  _ _ currentBalance _ _ _ _ _ _ _ Nothing) 
  = mulBR currentBalance (yearCountFraction DC_ACT_365F (max bd sd) ed) 

weightAverageBalance sd ed b@(Bond _ _ (OriginalInfo ob bd _ _ )  _ _ currentBalance _ _ _ _ _ _ _ (Just (S.Statement txns)))
  = S.weightAvgBalance' 
        (max bd sd) 
        ed 
        (DL.toList txns)

weightAverageBalance sd ed b@(MultiIntBond _ _ (OriginalInfo ob bd _ _ )  _ _ currentBalance _ _ _ _ _ _ _ (Just (S.Statement txns)))
  = S.weightAvgBalance' 
        (max bd sd) 
        ed 
        (DL.toList txns)


weightAverageBalance sd ed bg@(BondGroup bMap _)
  = sum $ weightAverageBalance sd ed <$> Map.elems bMap -- `debug` (">>>"++ show (weightAverageBalance sd ed <$> Map.elems bMap))


tryCalcZspread :: Rational -> Balance -> Date -> [(Date,Balance)] -> Ts -> Double -> Double
tryCalcZspread tradePrice originBalance priceDay futureCfs riskFreeCurve spread
  = let 
      pvCurve = shiftTsByAmt riskFreeCurve (fromRational (toRational spread))
      pvs = [ pv pvCurve priceDay _d _amt | (_d, _amt) <- futureCfs ]
      newPrice = 100 * sum pvs
      faceVal = divideBB newPrice originBalance
    in 
      fromRational (faceVal - tradePrice)


calcZspread :: (Rational,Date) -> Bond -> Ts -> Either String Spread
calcZspread _ b@Bond{bndStmt = Nothing} _ = Left "No Cashflow for bond"
calcZspread _ b@MultiIntBond{bndStmt = Nothing} _ = Left "No Cashflow for bond"
calcZspread (tradePrice,priceDay) b riskFreeCurve =
    let 
      txns = S.getAllTxns b
      bInfo = bndOriginInfo b
      (_,futureTxns) = splitByDate txns priceDay EqToRight
      cashflow = S.getTxnAmt <$> futureTxns
      ds = S.getDate <$> futureTxns
      oBalance = originBalance bInfo
      itertimes = 500
      def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.00001 }
    in
      case ridders def (0.0001,100) (tryCalcZspread tradePrice oBalance priceDay (zip ds cashflow) riskFreeCurve) of
        Root r -> Right (fromRational (toRational r))
        _ -> Left $ "Failed to find Z spread with "++ show itertimes ++ " times try"

-- ^ get total funded balance (from transaction) of a bond
totalFundedBalance :: Bond -> Balance
totalFundedBalance (BondGroup bMap _) = sum $ totalFundedBalance <$> Map.elems bMap
totalFundedBalance b
  = let 
      txns = S.getAllTxns b
      isFundingTxn (FundWith _ _) = True
      isFundingTxn _ = False
      fundingTxns = S.filterTxn isFundingTxn txns
    in 
      sum $ (\(BondTxn d b i p r0 c di dioi f t) -> abs p) <$> fundingTxns

buildRateResetDates :: Bond -> StartDate -> EndDate -> [Date]
buildRateResetDates (BondGroup bMap _) sd ed  =  concat $ (\x -> buildRateResetDates x sd ed) <$> Map.elems bMap
buildRateResetDates b@Bond{bndInterestInfo = ii,bndStepUp = mSt } sd ed 
  = let
      resetDp = getDpFromIntInfo ii 
      floaterRateResetDates (Just dp) = genSerialDatesTill2 NO_IE sd dp ed 
      floaterRateResetDates Nothing = []
    in 
      floaterRateResetDates resetDp

buildRateResetDates b@MultiIntBond{bndInterestInfos = iis} sd ed 
  = let 
      floaterRateResetDates (Just dp) = genSerialDatesTill2 NO_IE sd dp ed 
      floaterRateResetDates Nothing = []
    in 
      -- TODO: perf: sort and distinct
      concat $ (floaterRateResetDates . getDpFromIntInfo) <$> iis



buildStepUpDates :: Bond -> StartDate -> EndDate -> [Date]
buildStepUpDates (BondGroup bMap _) sd ed  =  concat $ (\x -> buildStepUpDates x sd ed) <$> Map.elems bMap
buildStepUpDates b@Bond{bndStepUp = mSt } sd ed 
  = case mSt of
      Nothing -> []
      Just (PassDateSpread d _) -> [d]
      Just (PassDateLadderSpread fstSd _ dp) -> genSerialDatesTill2 IE fstSd dp ed

buildStepUpDates b@MultiIntBond{bndStepUps = mSt } sd ed 
  = case mSt of
      Nothing -> []
      Just sts -> Set.toList $
                    Set.fromList $
                      concat $
                        (\y ->
                          case y of 
                            (PassDateLadderSpread fstSd _ dp) -> genSerialDatesTill2 IE fstSd dp ed
                            (PassDateSpread d _) -> [d]
                            ) <$> sts


instance S.QueryByComment Bond where 
  queryStmt Bond{bndStmt = Nothing} tc = []
  queryStmt MultiIntBond{bndStmt = Nothing} tc = []
  queryStmt Bond{bndStmt = Just (S.Statement txns)} tc
    = Data.List.filter (\x -> S.getTxnComment x == tc) (DL.toList txns)
  queryStmt MultiIntBond{bndStmt = Just (S.Statement txns)} tc
    = Data.List.filter (\x -> S.getTxnComment x == tc) (DL.toList txns)

instance Liable Bond where 

  isPaidOff b@Bond{bndBalance=bal, bndDueInt=di, bndDueIntOverInt=dioi}
    | bal==0 && di==0 && dioi==0 = True 
    | otherwise = False
  isPaidOff MultiIntBond{bndBalance=bal, bndDueInts=dis, bndDueIntOverInts=diois}
    | bal==0 && sum dis==0 && sum diois==0 = True 
    | otherwise = False  -- `debug` (bn ++ ":bal"++show bal++"dp"++show dp++"di"++show di)
  isPaidOff (BondGroup bMap _) = all (==True) $ isPaidOff <$> Map.elems bMap

  getCurBalance b@Bond {bndBalance = bal } = bal
  getCurBalance b@MultiIntBond {bndBalance = bal } = bal
  getCurBalance (BondGroup bMap _) = sum $ getCurBalance <$> Map.elems bMap

  getCurRate Bond{bndRate = r} = r
  getCurRate MultiIntBond{bndRates = rs} = sum rs
  getCurRate (BondGroup bMap _) = 
    fromRational $
      weightedBy
        (toRational . getCurBalance <$> Map.elems bMap)
        (toRational . getCurRate <$> Map.elems bMap)
  
  getOriginBalance (BondGroup bMap _) = sum $ getOriginBalance <$> Map.elems bMap
  getOriginBalance b = originBalance $ bndOriginInfo b

  getOriginDate b = originDate $ bndOriginInfo b

  getAccrueBegDate b = case bndDueIntDate b of
                        Just d -> d
                        Nothing -> getOriginDate b

  -- ^ get due int of a bond
  getDueInt b@Bond{bndDueInt=di} = di 
  getDueInt MultiIntBond{bndDueInts=dis} = sum dis
  getDueInt (BondGroup bMap _) = sum $ getDueInt <$> Map.elems bMap

  getDueIntAt MultiIntBond{bndDueInts=dis} idx = dis !! idx
  getDueIntOverIntAt MultiIntBond{bndDueIntOverInts=diois} idx = diois !! idx 
  getTotalDueIntAt b idx = getDueIntAt b idx + getDueIntOverIntAt b idx

  -- ^ get due IoI of a bond
  getDueIntOverInt b@Bond{bndDueIntOverInt=dioi} = dioi
  getDueIntOverInt MultiIntBond{bndDueIntOverInts=diois} = sum diois
  getDueIntOverInt (BondGroup bMap _) = sum $ getDueIntOverInt <$> Map.elems bMap

  -- ^ get total due interest of a bond (both due int and due IoI)
  getTotalDueInt b@Bond{bndDueInt=di,bndDueIntOverInt=dioi} = di + dioi
  getTotalDueInt MultiIntBond{bndDueInts=dis,bndDueIntOverInts=diois} = sum dis + sum diois
  getTotalDueInt (BondGroup bMap _ ) = sum $ getTotalDueInt <$> Map.elems bMap

  getOutstandingAmount b = getTotalDueInt b + getCurBalance b

instance IR.UseRate Bond where 
  isAdjustbleRate :: Bond -> Bool
  isAdjustbleRate Bond{bndInterestInfo = iinfo} = isAdjustble iinfo
  -- getIndex Bond{bndInterestInfo = iinfo }
  getIndexes Bond{bndInterestInfo = iinfo}  = getIndexFromInfo iinfo
  getIndexes (BondGroup bMap _)  = if Data.List.null combined then Nothing else Just combined
                                  where combined = concat . catMaybes  $ (\b -> getIndexFromInfo (bndInterestInfo b)) <$> Map.elems bMap
  getIndexes MultiIntBond{bndInterestInfos = iis} 
    = Just $ concat $ concat <$> getIndexFromInfo <$> iis

-- txnsLens :: Lens' Bond [Txn]
-- txnsLens = bndStmtLens . _Just . S.statementTxns
instance S.HasStmt Bond where 
  
  getAllTxns Bond{bndStmt = Nothing} = []
  getAllTxns Bond{bndStmt = Just (S.Statement txns)} = DL.toList txns
  getAllTxns MultiIntBond{bndStmt = Nothing} = []
  getAllTxns MultiIntBond{bndStmt = Just (S.Statement txns)} = DL.toList txns
  getAllTxns (BondGroup bMap _) = concat $ S.getAllTxns <$> Map.elems bMap

  hasEmptyTxn Bond{bndStmt = Nothing} = True
  hasEmptyTxn Bond{bndStmt = Just (S.Statement txn)} = txn == DL.empty
  hasEmptyTxn MultiIntBond{bndStmt = Nothing} = True
  hasEmptyTxn MultiIntBond{bndStmt = Just (S.Statement txn)} = txn == DL.empty
  hasEmptyTxn (BondGroup bMap _) = all S.hasEmptyTxn $ Map.elems bMap
  hasEmptyTxn _ = False


makeLensesFor [("bndType","bndTypeLens"),("bndOriginInfo","bndOriginInfoLens"),("bndInterestInfo","bndIntLens"),("bndStmt","bndStmtLens")] ''Bond
makeLensesFor [("bndOriginDate","bndOriginDateLens"),("bndOriginBalance","bndOriginBalanceLens"),("bndOriginRate","bndOriginRateLens")] ''OriginalInfo

makePrisms ''Bond

$(deriveJSON defaultOptions ''InterestOverInterestType)
$(deriveJSON defaultOptions ''InterestInfo)
$(deriveJSON defaultOptions ''OriginalInfo)
$(deriveJSON defaultOptions ''BondType)
$(deriveJSON defaultOptions ''StepUp)
$(deriveJSON defaultOptions ''Bond)