    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE ParallelListComp  #-}
    {-# LANGUAGE TemplateHaskell   #-}
    {-# LANGUAGE TupleSections     #-}
    {-# LANGUAGE RecordWildCards   #-}
    {-# LANGUAGE ViewPatterns #-}
    {-|

    The @roi@ command prints internal rate of return and time-weighted rate of return for and investment.

    -}

    module Hledger.Cli.Commands.Roi (
      roimode
      , roi
    ) where

    import Control.Monad
    import System.Exit
    import Data.Time.Calendar
    import Text.Printf
    import Data.Bifunctor (second)
    import Data.Either (fromLeft, fromRight, isLeft)
    import Data.Function (on)
    import Data.List
    import Numeric.RootFinding
    import Data.Decimal
    import qualified Data.Text as T
    import qualified Data.Text.Lazy.IO as TL
    import System.Console.CmdArgs.Explicit as CmdArgs

    import Text.Tabular.AsciiWide as Tab

    import Hledger
    import Hledger.Cli.CliOptions

    roimode :: Mode RawOpts
    roimode = [Char]
    -> [Flag RawOpts]
    -> [([Char], [Flag RawOpts])]
    -> [Flag RawOpts]
    -> ([Arg RawOpts], Maybe (Arg RawOpts))
    -> Mode RawOpts
    hledgerCommandMode
      $(embedFileRelative "Hledger/Cli/Commands/Roi.txt")
      [[[Char]] -> (RawOpts -> RawOpts) -> [Char] -> Flag RawOpts
    forall a. [[Char]] -> (a -> a) -> [Char] -> Flag a
    flagNone [[Char]
    "cashflow"] ([Char] -> RawOpts -> RawOpts
    setboolopt [Char]
    "cashflow") [Char]
    "show all amounts that were used to compute returns"
      ,[[Char]] -> Update RawOpts -> [Char] -> [Char] -> Flag RawOpts
    forall a. [[Char]] -> Update a -> [Char] -> [Char] -> Flag a
    flagReq [[Char]
    "investment"] (\[Char]
    s RawOpts
    opts -> RawOpts -> Either [Char] RawOpts
    forall a b. b -> Either a b
    Right (RawOpts -> Either [Char] RawOpts)
    -> RawOpts -> Either [Char] RawOpts
    forall a b. (a -> b) -> a -> b
    $ [Char] -> [Char] -> RawOpts -> RawOpts
    setopt [Char]
    "investment" [Char]
    s RawOpts
    opts) [Char]
    "QUERY"
        [Char]
    "query to select your investment transactions"
      ,[[Char]] -> Update RawOpts -> [Char] -> [Char] -> Flag RawOpts
    forall a. [[Char]] -> Update a -> [Char] -> [Char] -> Flag a
    flagReq [[Char]
    "profit-loss",[Char]
    "pnl"] (\[Char]
    s RawOpts
    opts -> RawOpts -> Either [Char] RawOpts
    forall a b. b -> Either a b
    Right (RawOpts -> Either [Char] RawOpts)
    -> RawOpts -> Either [Char] RawOpts
    forall a b. (a -> b) -> a -> b
    $ [Char] -> [Char] -> RawOpts -> RawOpts
    setopt [Char]
    "pnl" [Char]
    s RawOpts
    opts) [Char]
    "QUERY"
        [Char]
    "query to select profit-and-loss or appreciation/valuation transactions"
      ]
      [([Char], [Flag RawOpts])
    generalflagsgroup1]
      [Flag RawOpts]
    hiddenflags
      ([], Arg RawOpts -> Maybe (Arg RawOpts)
    forall a. a -> Maybe a
    Just (Arg RawOpts -> Maybe (Arg RawOpts))
    -> Arg RawOpts -> Maybe (Arg RawOpts)
    forall a b. (a -> b) -> a -> b
    $ [Char] -> Arg RawOpts
    argsFlag [Char]
    "[QUERY]")

    -- One reporting span,
    data OneSpan = OneSpan
      Day -- start date, inclusive
      Day   -- end date, exclusive
      MixedAmount -- value of investment at the beginning of day on spanBegin_
      MixedAmount -- value of investment at the end of day on spanEnd_
      [(Day,MixedAmount)] -- all deposits and withdrawals (but not changes of value) in the DateSpan [spanBegin_,spanEnd_)
      [(Day,MixedAmount)] -- all PnL changes of the value of investment in the DateSpan [spanBegin_,spanEnd_)
     deriving (Int -> OneSpan -> [Char] -> [Char]
    [OneSpan] -> [Char] -> [Char]
    OneSpan -> [Char]
    (Int -> OneSpan -> [Char] -> [Char])
    -> (OneSpan -> [Char])
    -> ([OneSpan] -> [Char] -> [Char])
    -> Show OneSpan
    forall a.
    (Int -> a -> [Char] -> [Char])
    -> (a -> [Char]) -> ([a] -> [Char] -> [Char]) -> Show a
    $cshowsPrec :: Int -> OneSpan -> [Char] -> [Char]
    showsPrec :: Int -> OneSpan -> [Char] -> [Char]
    $cshow :: OneSpan -> [Char]
    show :: OneSpan -> [Char]
    $cshowList :: [OneSpan] -> [Char] -> [Char]
    showList :: [OneSpan] -> [Char] -> [Char]
    Show)

    roi ::  CliOpts -> Journal -> IO ()
    roi :: CliOpts -> Journal -> IO ()
    roi CliOpts{rawopts_ :: CliOpts -> RawOpts
    rawopts_=RawOpts
    rawopts, reportspec_ :: CliOpts -> ReportSpec
    reportspec_=rspec :: ReportSpec
    rspec@ReportSpec{_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts{Bool
    Int
    [Text]
    [Status]
    Maybe Int
    Maybe Text
    Maybe NormalSign
    Maybe ValuationType
    Maybe ConversionOp
    Interval
    Period
    StringFormat
    Layout
    AccountListMode
    BalanceAccumulation
    BalanceCalculation
    period_ :: Period
    interval_ :: Interval
    statuses_ :: [Status]
    conversionop_ :: Maybe ConversionOp
    value_ :: Maybe ValuationType
    infer_prices_ :: Bool
    depth_ :: Maybe Int
    date2_ :: Bool
    empty_ :: Bool
    no_elide_ :: Bool
    real_ :: Bool
    format_ :: StringFormat
    pretty_ :: Bool
    querystring_ :: [Text]
    average_ :: Bool
    related_ :: Bool
    txn_dates_ :: Bool
    balancecalc_ :: BalanceCalculation
    balanceaccum_ :: BalanceAccumulation
    budgetpat_ :: Maybe Text
    accountlistmode_ :: AccountListMode
    drop_ :: Int
    declared_ :: Bool
    row_total_ :: Bool
    no_total_ :: Bool
    show_costs_ :: Bool
    sort_amount_ :: Bool
    percent_ :: Bool
    invert_ :: Bool
    normalbalance_ :: Maybe NormalSign
    color_ :: Bool
    transpose_ :: Bool
    layout_ :: Layout
    period_ :: ReportOpts -> Period
    interval_ :: ReportOpts -> Interval
    statuses_ :: ReportOpts -> [Status]
    conversionop_ :: ReportOpts -> Maybe ConversionOp
    value_ :: ReportOpts -> Maybe ValuationType
    infer_prices_ :: ReportOpts -> Bool
    depth_ :: ReportOpts -> Maybe Int
    date2_ :: ReportOpts -> Bool
    empty_ :: ReportOpts -> Bool
    no_elide_ :: ReportOpts -> Bool
    real_ :: ReportOpts -> Bool
    format_ :: ReportOpts -> StringFormat
    pretty_ :: ReportOpts -> Bool
    querystring_ :: ReportOpts -> [Text]
    average_ :: ReportOpts -> Bool
    related_ :: ReportOpts -> Bool
    txn_dates_ :: ReportOpts -> Bool
    balancecalc_ :: ReportOpts -> BalanceCalculation
    balanceaccum_ :: ReportOpts -> BalanceAccumulation
    budgetpat_ :: ReportOpts -> Maybe Text
    accountlistmode_ :: ReportOpts -> AccountListMode
    drop_ :: ReportOpts -> Int
    declared_ :: ReportOpts -> Bool
    row_total_ :: ReportOpts -> Bool
    no_total_ :: ReportOpts -> Bool
    show_costs_ :: ReportOpts -> Bool
    sort_amount_ :: ReportOpts -> Bool
    percent_ :: ReportOpts -> Bool
    invert_ :: ReportOpts -> Bool
    normalbalance_ :: ReportOpts -> Maybe NormalSign
    color_ :: ReportOpts -> Bool
    transpose_ :: ReportOpts -> Bool
    layout_ :: ReportOpts -> Layout
    ..}}} Journal
    j = do
      -- We may be converting posting amounts to value, per hledger_options.m4.md "Effect of --value on reports".
      let
        today :: Day
    today = ReportSpec -> Day
    _rsDay ReportSpec
    rspec
        priceOracle :: PriceOracle
    priceOracle = Bool -> Journal -> PriceOracle
    journalPriceOracle Bool
    infer_prices_ Journal
    j
        styles :: Map Text AmountStyle
    styles = Journal -> Map Text AmountStyle
    journalCommodityStyles Journal
    j
        mixedAmountValue :: Day -> Day -> MixedAmount -> MixedAmount
    mixedAmountValue Day
    periodlast Day
    date =
            (MixedAmount -> MixedAmount)
    -> (ValuationType -> MixedAmount -> MixedAmount)
    -> Maybe ValuationType
    -> MixedAmount
    -> MixedAmount
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe MixedAmount -> MixedAmount
    forall a. a -> a
    id (PriceOracle
    -> Map Text AmountStyle
    -> Day
    -> Day
    -> Day
    -> ValuationType
    -> MixedAmount
    -> MixedAmount
    mixedAmountApplyValuation PriceOracle
    priceOracle Map Text AmountStyle
    styles Day
    periodlast Day
    today Day
    date) Maybe ValuationType
    value_
          (MixedAmount -> MixedAmount)
    -> (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (MixedAmount -> MixedAmount)
    -> (ConversionOp -> MixedAmount -> MixedAmount)
    -> Maybe ConversionOp
    -> MixedAmount
    -> MixedAmount
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe MixedAmount -> MixedAmount
    forall a. a -> a
    id (Map Text AmountStyle -> ConversionOp -> MixedAmount -> MixedAmount
    mixedAmountToCost Map Text AmountStyle
    styles) Maybe ConversionOp
    conversionop_

      let
        ropts :: ReportOpts
    ropts = ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec
        wd :: WhichDate
    wd = ReportOpts -> WhichDate
    whichDate ReportOpts
    ropts
        showCashFlow :: Bool
    showCashFlow = [Char] -> RawOpts -> Bool
    boolopt [Char]
    "cashflow" RawOpts
    rawopts
        prettyTables :: Bool
    prettyTables = Bool
    pretty_
        makeQuery :: [Char] -> m Query
    makeQuery [Char]
    flag = do
            Query
    q <- ([Char] -> m Query)
    -> ((Query, [QueryOpt]) -> m Query)
    -> Either [Char] (Query, [QueryOpt])
    -> m Query
    forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
    either [Char] -> m Query
    forall a. [Char] -> a
    usageError (Query -> m Query
    forall a. a -> m a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Query -> m Query)
    -> ((Query, [QueryOpt]) -> Query) -> (Query, [QueryOpt]) -> m Query
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Query, [QueryOpt]) -> Query
    forall a b. (a, b) -> a
    fst) (Either [Char] (Query, [QueryOpt]) -> m Query)
    -> ([Char] -> Either [Char] (Query, [QueryOpt]))
    -> [Char]
    -> m Query
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> Text -> Either [Char] (Query, [QueryOpt])
    parseQuery Day
    today (Text -> Either [Char] (Query, [QueryOpt]))
    -> ([Char] -> Text) -> [Char] -> Either [Char] (Query, [QueryOpt])
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [Char] -> Text
    T.pack ([Char] -> m Query) -> [Char] -> m Query
    forall a b. (a -> b) -> a -> b
    $ [Char] -> RawOpts -> [Char]
    stringopt [Char]
    flag RawOpts
    rawopts
            Query -> m Query
    forall a. a -> m a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Query -> m Query) -> (Query -> Query) -> Query -> m Query
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Query -> Query
    simplifyQuery (Query -> m Query) -> Query -> m Query
    forall a b. (a -> b) -> a -> b
    $ [Query] -> Query
    And [ReportOpts -> Query
    queryFromFlags ReportOpts
    ropts{period_ :: Period
    period_=Period
    PeriodAll}, Query
    q]

      Query
    investmentsQuery <- [Char] -> IO Query
    forall {m :: * -> *}. Monad m => [Char] -> m Query
    makeQuery [Char]
    "investment"
      Query
    pnlQuery         <- [Char] -> IO Query
    forall {m :: * -> *}. Monad m => [Char] -> m Query
    makeQuery [Char]
    "pnl"

      let
        filteredj :: Journal
    filteredj = Query -> Journal -> Journal
    filterJournalTransactions Query
    investmentsQuery Journal
    j
        trans :: [Transaction]
    trans = [Char] -> [Transaction] -> [Transaction]
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "investments" ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $ Journal -> [Transaction]
    jtxns Journal
    filteredj

      Bool -> IO () -> IO ()
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    when ([Transaction] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [Transaction]
    trans) (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ do
        [Char] -> IO ()
    putStrLn [Char]
    "No relevant transactions found. Check your investments query"
        IO ()
    forall a. IO a
    exitFailure

      let spans :: [DateSpan]
    spans = (DateSpan, [DateSpan]) -> [DateSpan]
    forall a b. (a, b) -> b
    snd ((DateSpan, [DateSpan]) -> [DateSpan])
    -> (DateSpan, [DateSpan]) -> [DateSpan]
    forall a b. (a -> b) -> a -> b
    $ Journal -> ReportSpec -> (DateSpan, [DateSpan])
    reportSpan Journal
    filteredj ReportSpec
    rspec

      let priceDirectiveDates :: [Day]
    priceDirectiveDates = [Char] -> [Day] -> [Day]
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "priceDirectiveDates" ([Day] -> [Day]) -> [Day] -> [Day]
    forall a b. (a -> b) -> a -> b
    $ (PriceDirective -> Day) -> [PriceDirective] -> [Day]
    forall a b. (a -> b) -> [a] -> [b]
    map PriceDirective -> Day
    pddate ([PriceDirective] -> [Day]) -> [PriceDirective] -> [Day]
    forall a b. (a -> b) -> a -> b
    $ Journal -> [PriceDirective]
    jpricedirectives Journal
    j

      [[Text]]
    tableBody <- [DateSpan] -> (DateSpan -> IO [Text]) -> IO [[Text]]
    forall (t :: * -> *) (m :: * -> *) a b.
    (Traversable t, Monad m) =>
    t a -> (a -> m b) -> m (t b)
    forM [DateSpan]
    spans ((DateSpan -> IO [Text]) -> IO [[Text]])
    -> (DateSpan -> IO [Text]) -> IO [[Text]]
    forall a b. (a -> b) -> a -> b
    $ \spn :: DateSpan
    spn@(DateSpan (Just EFDay
    begin) (Just EFDay
    end)) -> do
        -- Spans are [begin,end), and end is 1 day after the actual end date we are interested in
        let
          b :: Day
    b = EFDay -> Day
    fromEFDay EFDay
    begin
          e :: Day
    e = EFDay -> Day
    fromEFDay EFDay
    end
          cashFlowApplyCostValue :: [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    cashFlowApplyCostValue = ((Day, MixedAmount) -> (Day, MixedAmount))
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\(Day
    d,MixedAmount
    amt) -> (Day
    d,Day -> Day -> MixedAmount -> MixedAmount
    mixedAmountValue Day
    e Day
    d MixedAmount
    amt))

          valueBefore :: MixedAmount
    valueBefore =
            Day -> Day -> MixedAmount -> MixedAmount
    mixedAmountValue Day
    e Day
    b (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $
            [Transaction] -> Query -> MixedAmount
    total [Transaction]
    trans ([Query] -> Query
    And [ Query
    investmentsQuery
                             , DateSpan -> Query
    Date (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just EFDay
    begin))])

          valueAfter :: MixedAmount
    valueAfter  =
            Day -> Day -> MixedAmount -> MixedAmount
    mixedAmountValue Day
    e Day
    e (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $
            [Transaction] -> Query -> MixedAmount
    total [Transaction]
    trans ([Query] -> Query
    And [Query
    investmentsQuery
                             , DateSpan -> Query
    Date (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just EFDay
    end))])

          priceDates :: [Day]
    priceDates = [Char] -> [Day] -> [Day]
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "priceDates" ([Day] -> [Day]) -> [Day] -> [Day]
    forall a b. (a -> b) -> a -> b
    $ [Day] -> [Day]
    forall a. Eq a => [a] -> [a]
    nub ([Day] -> [Day]) -> [Day] -> [Day]
    forall a b. (a -> b) -> a -> b
    $ (Day -> Bool) -> [Day] -> [Day]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (DateSpan -> Day -> Bool
    spanContainsDate DateSpan
    spn) [Day]
    priceDirectiveDates
          cashFlow :: [(Day, MixedAmount)]
    cashFlow =
            (((Day -> (Day, MixedAmount)) -> [Day] -> [(Day, MixedAmount)]
    forall a b. (a -> b) -> [a] -> [b]
    map (,MixedAmount
    nullmixedamt) [Day]
    priceDates)[(Day, MixedAmount)]
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a. [a] -> [a] -> [a]
    ++) ([(Day, MixedAmount)] -> [(Day, MixedAmount)])
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $
            [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    cashFlowApplyCostValue ([(Day, MixedAmount)] -> [(Day, MixedAmount)])
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $
            WhichDate -> [Transaction] -> Query -> [(Day, MixedAmount)]
    calculateCashFlow WhichDate
    wd [Transaction]
    trans ([Query] -> Query
    And [ Query -> Query
    Not Query
    investmentsQuery
                                            , Query -> Query
    Not Query
    pnlQuery
                                            , DateSpan -> Query
    Date DateSpan
    spn ] )

          pnl :: [(Day, MixedAmount)]
    pnl =
            [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    cashFlowApplyCostValue ([(Day, MixedAmount)] -> [(Day, MixedAmount)])
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $
            WhichDate -> [Transaction] -> Query -> [(Day, MixedAmount)]
    calculateCashFlow WhichDate
    wd [Transaction]
    trans ([Query] -> Query
    And [ Query -> Query
    Not Query
    investmentsQuery
                                            , Query
    pnlQuery
                                            , DateSpan -> Query
    Date DateSpan
    spn ] )

          thisSpan :: OneSpan
    thisSpan = [Char] -> OneSpan -> OneSpan
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "processing span" (OneSpan -> OneSpan) -> OneSpan -> OneSpan
    forall a b. (a -> b) -> a -> b
    $
                     Day
    -> Day
    -> MixedAmount
    -> MixedAmount
    -> [(Day, MixedAmount)]
    -> [(Day, MixedAmount)]
    -> OneSpan
    OneSpan Day
    b Day
    e MixedAmount
    valueBefore MixedAmount
    valueAfter [(Day, MixedAmount)]
    cashFlow [(Day, MixedAmount)]
    pnl

        Double
    irr <- Bool -> Bool -> OneSpan -> IO Double
    internalRateOfReturn Bool
    showCashFlow Bool
    prettyTables OneSpan
    thisSpan
        Double
    twr <- Bool
    -> Bool
    -> Query
    -> [Transaction]
    -> (Day -> Day -> MixedAmount -> MixedAmount)
    -> OneSpan
    -> IO Double
    timeWeightedReturn Bool
    showCashFlow Bool
    prettyTables Query
    investmentsQuery [Transaction]
    trans Day -> Day -> MixedAmount -> MixedAmount
    mixedAmountValue OneSpan
    thisSpan
        let cashFlowAmt :: MixedAmount
    cashFlowAmt = MixedAmount -> MixedAmount
    maNegate (MixedAmount -> MixedAmount)
    -> ([MixedAmount] -> MixedAmount) -> [MixedAmount] -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [MixedAmount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t MixedAmount -> MixedAmount
    maSum ([MixedAmount] -> MixedAmount) -> [MixedAmount] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ ((Day, MixedAmount) -> MixedAmount)
    -> [(Day, MixedAmount)] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map (Day, MixedAmount) -> MixedAmount
    forall a b. (a, b) -> b
    snd [(Day, MixedAmount)]
    cashFlow
        let smallIsZero :: a -> a
    smallIsZero a
    x = if a -> a
    forall a. Num a => a -> a
    abs a
    x a -> a -> Bool
    forall a. Ord a => a -> a -> Bool
    < a
    0.01 then a
    0.0 else a
    x
        [Text] -> IO [Text]
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return [ Day -> Text
    showDate Day
    b
               , Day -> Text
    showDate (Integer -> Day -> Day
    addDays (-Integer
    1) Day
    e)
               , [Char] -> Text
    T.pack ([Char] -> Text) -> [Char] -> Text
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Char]
    showMixedAmount MixedAmount
    valueBefore
               , [Char] -> Text
    T.pack ([Char] -> Text) -> [Char] -> Text
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Char]
    showMixedAmount MixedAmount
    cashFlowAmt
               , [Char] -> Text
    T.pack ([Char] -> Text) -> [Char] -> Text
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Char]
    showMixedAmount MixedAmount
    valueAfter
               , [Char] -> Text
    T.pack ([Char] -> Text) -> [Char] -> Text
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Char]
    showMixedAmount (MixedAmount
    valueAfter MixedAmount -> MixedAmount -> MixedAmount
    `maMinus` (MixedAmount
    valueBefore MixedAmount -> MixedAmount -> MixedAmount
    `maPlus` MixedAmount
    cashFlowAmt))
               , [Char] -> Text
    T.pack ([Char] -> Text) -> [Char] -> Text
    forall a b. (a -> b) -> a -> b
    $ [Char] -> Double -> [Char]
    forall r. PrintfType r => [Char] -> r
    printf [Char]
    "%0.2f%%" (Double -> [Char]) -> Double -> [Char]
    forall a b. (a -> b) -> a -> b
    $ Double -> Double
    forall {a}. (Ord a, Fractional a) => a -> a
    smallIsZero Double
    irr
               , [Char] -> Text
    T.pack ([Char] -> Text) -> [Char] -> Text
    forall a b. (a -> b) -> a -> b
    $ [Char] -> Double -> [Char]
    forall r. PrintfType r => [Char] -> r
    printf [Char]
    "%0.2f%%" (Double -> [Char]) -> Double -> [Char]
    forall a b. (a -> b) -> a -> b
    $ Double -> Double
    forall {a}. (Ord a, Fractional a) => a -> a
    smallIsZero Double
    twr ]

      let table :: Table Text Text Text
    table = Header Text -> Header Text -> [[Text]] -> Table Text Text Text
    forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
    Table
                  (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.NoLine ((Integer -> Header Text) -> [Integer] -> [Header Text]
    forall a b. (a -> b) -> [a] -> [b]
    map (Text -> Header Text
    forall h. h -> Header h
    Header (Text -> Header Text)
    -> (Integer -> Text) -> Integer -> Header Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [Char] -> Text
    T.pack ([Char] -> Text) -> (Integer -> [Char]) -> Integer -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Integer -> [Char]
    forall a. Show a => a -> [Char]
    show) (Int -> [Integer] -> [Integer]
    forall a. Int -> [a] -> [a]
    take ([[Text]] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [[Text]]
    tableBody) [Integer
    1..])))
                  (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.DoubleLine
                   [ Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.SingleLine [Text -> Header Text
    forall h. h -> Header h
    Header Text
    "Begin", Text -> Header Text
    forall h. h -> Header h
    Header Text
    "End"]
                   , Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.SingleLine [Text -> Header Text
    forall h. h -> Header h
    Header Text
    "Value (begin)", Text -> Header Text
    forall h. h -> Header h
    Header Text
    "Cashflow", Text -> Header Text
    forall h. h -> Header h
    Header Text
    "Value (end)", Text -> Header Text
    forall h. h -> Header h
    Header Text
    "PnL"]
                   , Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.SingleLine [Text -> Header Text
    forall h. h -> Header h
    Header Text
    "IRR", Text -> Header Text
    forall h. h -> Header h
    Header Text
    "TWR"]])
                  [[Text]]
    tableBody

      Text -> IO ()
    TL.putStrLn (Text -> IO ()) -> Text -> IO ()
    forall a b. (a -> b) -> a -> b
    $ Bool
    -> (Text -> Text)
    -> (Text -> Text)
    -> (Text -> Text)
    -> Table Text Text Text
    -> Text
    forall rh ch a.
    Bool
    -> (rh -> Text)
    -> (ch -> Text)
    -> (a -> Text)
    -> Table rh ch a
    -> Text
    Tab.render Bool
    prettyTables Text -> Text
    forall a. a -> a
    id Text -> Text
    forall a. a -> a
    id Text -> Text
    forall a. a -> a
    id Table Text Text Text
    table

    timeWeightedReturn :: Bool
    -> Bool
    -> Query
    -> [Transaction]
    -> (Day -> Day -> MixedAmount -> MixedAmount)
    -> OneSpan
    -> IO Double
    timeWeightedReturn Bool
    showCashFlow Bool
    prettyTables Query
    investmentsQuery [Transaction]
    trans Day -> Day -> MixedAmount -> MixedAmount
    mixedAmountValue (OneSpan Day
    begin Day
    end MixedAmount
    valueBeforeAmt MixedAmount
    valueAfter [(Day, MixedAmount)]
    cashFlow [(Day, MixedAmount)]
    pnl) = do
      let valueBefore :: Decimal
    valueBefore = MixedAmount -> Decimal
    unMix MixedAmount
    valueBeforeAmt
      let initialUnitPrice :: Decimal
    initialUnitPrice = Decimal
    100 :: Decimal
      let initialUnits :: Decimal
    initialUnits = Decimal
    valueBefore Decimal -> Decimal -> Decimal
    forall a. Fractional a => a -> a -> a
    / Decimal
    initialUnitPrice
      let changes :: [(Day, Either MixedAmount MixedAmount)]
    changes =
            -- If cash flow and PnL changes happen on the same day, this
            -- will sort PnL changes to come before cash flows (on any
            -- given day), so that we will have better unit price computed
            -- first for processing cash flow. This is why pnl changes are Left
            -- and cashflows are Right.
            -- However, if the very first date in the changes list has both
            -- PnL and CashFlow, we would not be able to apply pnl change to 0 unit,
            -- which would lead to an error. We make sure that we have at least one
            -- cashflow entry at the front, and we know that there would be at most
            -- one for the given date, by construction. Empty CashFlows added
            -- because of a begin date before the first transaction are not seen as
            -- a valid cashflow entry at the front.
            [(Day, Either MixedAmount MixedAmount)]
    -> [(Day, Either MixedAmount MixedAmount)]
    forall {a} {a}.
    [(a, Either a MixedAmount)] -> [(a, Either a MixedAmount)]
    zeroUnitsNeedsCashflowAtTheFront
            ([(Day, Either MixedAmount MixedAmount)]
     -> [(Day, Either MixedAmount MixedAmount)])
    -> [(Day, Either MixedAmount MixedAmount)]
    -> [(Day, Either MixedAmount MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ [(Day, Either MixedAmount MixedAmount)]
    -> [(Day, Either MixedAmount MixedAmount)]
    forall a. Ord a => [a] -> [a]
    sort
            ([(Day, Either MixedAmount MixedAmount)]
     -> [(Day, Either MixedAmount MixedAmount)])
    -> [(Day, Either MixedAmount MixedAmount)]
    -> [(Day, Either MixedAmount MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ [(Day, Either MixedAmount MixedAmount)]
    forall {a}. [(Day, Either a MixedAmount)]
    datedCashflows [(Day, Either MixedAmount MixedAmount)]
    -> [(Day, Either MixedAmount MixedAmount)]
    -> [(Day, Either MixedAmount MixedAmount)]
    forall a. [a] -> [a] -> [a]
    ++ [(Day, Either MixedAmount MixedAmount)]
    forall {b}. [(Day, Either MixedAmount b)]
    datedPnls
            where
              zeroUnitsNeedsCashflowAtTheFront :: [(a, Either a MixedAmount)] -> [(a, Either a MixedAmount)]
    zeroUnitsNeedsCashflowAtTheFront [(a, Either a MixedAmount)]
    changes1 =
                if Decimal
    initialUnits Decimal -> Decimal -> Bool
    forall a. Ord a => a -> a -> Bool
    > Decimal
    0 then [(a, Either a MixedAmount)]
    changes1
                else
                  let ([(a, Either a MixedAmount)]
    leadingEmptyCashFlows, [(a, Either a MixedAmount)]
    rest) = ((a, Either a MixedAmount) -> Bool)
    -> [(a, Either a MixedAmount)]
    -> ([(a, Either a MixedAmount)], [(a, Either a MixedAmount)])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    span (a, Either a MixedAmount) -> Bool
    forall {a} {a}. (a, Either a MixedAmount) -> Bool
    isEmptyCashflow [(a, Either a MixedAmount)]
    changes1
                      ([(a, Either a MixedAmount)]
    leadingPnls, [(a, Either a MixedAmount)]
    rest') = ((a, Either a MixedAmount) -> Bool)
    -> [(a, Either a MixedAmount)]
    -> ([(a, Either a MixedAmount)], [(a, Either a MixedAmount)])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    span (Either a MixedAmount -> Bool
    forall a b. Either a b -> Bool
    isLeft (Either a MixedAmount -> Bool)
    -> ((a, Either a MixedAmount) -> Either a MixedAmount)
    -> (a, Either a MixedAmount)
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (a, Either a MixedAmount) -> Either a MixedAmount
    forall a b. (a, b) -> b
    snd) [(a, Either a MixedAmount)]
    rest
                      ([(a, Either a MixedAmount)]
    firstCashflow, [(a, Either a MixedAmount)]
    rest'') = Int
    -> [(a, Either a MixedAmount)]
    -> ([(a, Either a MixedAmount)], [(a, Either a MixedAmount)])
    forall a. Int -> [a] -> ([a], [a])
    splitAt Int
    1 [(a, Either a MixedAmount)]
    rest'
                  in [(a, Either a MixedAmount)]
    leadingEmptyCashFlows [(a, Either a MixedAmount)]
    -> [(a, Either a MixedAmount)] -> [(a, Either a MixedAmount)]
    forall a. [a] -> [a] -> [a]
    ++ [(a, Either a MixedAmount)]
    firstCashflow [(a, Either a MixedAmount)]
    -> [(a, Either a MixedAmount)] -> [(a, Either a MixedAmount)]
    forall a. [a] -> [a] -> [a]
    ++ [(a, Either a MixedAmount)]
    leadingPnls [(a, Either a MixedAmount)]
    -> [(a, Either a MixedAmount)] -> [(a, Either a MixedAmount)]
    forall a. [a] -> [a] -> [a]
    ++ [(a, Either a MixedAmount)]
    rest''

              isEmptyCashflow :: (a, Either a MixedAmount) -> Bool
    isEmptyCashflow (a
    _date, Either a MixedAmount
    amt) = case Either a MixedAmount
    amt of
                Right MixedAmount
    amt' -> MixedAmount -> Bool
    mixedAmountIsZero MixedAmount
    amt'
                Left a
    _     -> Bool
    False

              datedPnls :: [(Day, Either MixedAmount b)]
    datedPnls = ((Day, MixedAmount) -> (Day, Either MixedAmount b))
    -> [(Day, MixedAmount)] -> [(Day, Either MixedAmount b)]
    forall a b. (a -> b) -> [a] -> [b]
    map ((MixedAmount -> Either MixedAmount b)
    -> (Day, MixedAmount) -> (Day, Either MixedAmount b)
    forall b c a. (b -> c) -> (a, b) -> (a, c)
    forall (p :: * -> * -> *) b c a.
    Bifunctor p =>
    (b -> c) -> p a b -> p a c
    second MixedAmount -> Either MixedAmount b
    forall a b. a -> Either a b
    Left) ([(Day, MixedAmount)] -> [(Day, Either MixedAmount b)])
    -> [(Day, MixedAmount)] -> [(Day, Either MixedAmount b)]
    forall a b. (a -> b) -> a -> b
    $ [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall {b}. Ord b => [(b, MixedAmount)] -> [(b, MixedAmount)]
    aggregateByDate [(Day, MixedAmount)]
    pnl

              datedCashflows :: [(Day, Either a MixedAmount)]
    datedCashflows = ((Day, MixedAmount) -> (Day, Either a MixedAmount))
    -> [(Day, MixedAmount)] -> [(Day, Either a MixedAmount)]
    forall a b. (a -> b) -> [a] -> [b]
    map ((MixedAmount -> Either a MixedAmount)
    -> (Day, MixedAmount) -> (Day, Either a MixedAmount)
    forall b c a. (b -> c) -> (a, b) -> (a, c)
    forall (p :: * -> * -> *) b c a.
    Bifunctor p =>
    (b -> c) -> p a b -> p a c
    second MixedAmount -> Either a MixedAmount
    forall a b. b -> Either a b
    Right) ([(Day, MixedAmount)] -> [(Day, Either a MixedAmount)])
    -> [(Day, MixedAmount)] -> [(Day, Either a MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall {b}. Ord b => [(b, MixedAmount)] -> [(b, MixedAmount)]
    aggregateByDate [(Day, MixedAmount)]
    cashFlow

              aggregateByDate :: [(b, MixedAmount)] -> [(b, MixedAmount)]
    aggregateByDate [(b, MixedAmount)]
    datedAmounts =
                -- Aggregate all entries for a single day, assuming that intraday interest is negligible
                [(b, MixedAmount)] -> [(b, MixedAmount)]
    forall a. Ord a => [a] -> [a]
    sort
                ([(b, MixedAmount)] -> [(b, MixedAmount)])
    -> [(b, MixedAmount)] -> [(b, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ ([(b, MixedAmount)] -> (b, MixedAmount))
    -> [[(b, MixedAmount)]] -> [(b, MixedAmount)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\[(b, MixedAmount)]
    date_cash -> let ([b]
    dates, [MixedAmount]
    cash) = [(b, MixedAmount)] -> ([b], [MixedAmount])
    forall a b. [(a, b)] -> ([a], [b])
    unzip [(b, MixedAmount)]
    date_cash in ([b] -> b
    forall a. HasCallStack => [a] -> a
    head [b]
    dates, [MixedAmount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t MixedAmount -> MixedAmount
    maSum [MixedAmount]
    cash))
                ([[(b, MixedAmount)]] -> [(b, MixedAmount)])
    -> [[(b, MixedAmount)]] -> [(b, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ ((b, MixedAmount) -> (b, MixedAmount) -> Bool)
    -> [(b, MixedAmount)] -> [[(b, MixedAmount)]]
    forall a. (a -> a -> Bool) -> [a] -> [[a]]
    groupBy (b -> b -> Bool
    forall a. Eq a => a -> a -> Bool
    (==) (b -> b -> Bool)
    -> ((b, MixedAmount) -> b)
    -> (b, MixedAmount)
    -> (b, MixedAmount)
    -> Bool
    forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
    `on` (b, MixedAmount) -> b
    forall a b. (a, b) -> a
    fst)
                ([(b, MixedAmount)] -> [[(b, MixedAmount)]])
    -> [(b, MixedAmount)] -> [[(b, MixedAmount)]]
    forall a b. (a -> b) -> a -> b
    $ ((b, MixedAmount) -> b) -> [(b, MixedAmount)] -> [(b, MixedAmount)]
    forall b a. Ord b => (a -> b) -> [a] -> [a]
    sortOn (b, MixedAmount) -> b
    forall a b. (a, b) -> a
    fst
                ([(b, MixedAmount)] -> [(b, MixedAmount)])
    -> [(b, MixedAmount)] -> [(b, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ ((b, MixedAmount) -> (b, MixedAmount))
    -> [(b, MixedAmount)] -> [(b, MixedAmount)]
    forall a b. (a -> b) -> [a] -> [b]
    map ((MixedAmount -> MixedAmount)
    -> (b, MixedAmount) -> (b, MixedAmount)
    forall b c a. (b -> c) -> (a, b) -> (a, c)
    forall (p :: * -> * -> *) b c a.
    Bifunctor p =>
    (b -> c) -> p a b -> p a c
    second MixedAmount -> MixedAmount
    maNegate)
                ([(b, MixedAmount)] -> [(b, MixedAmount)])
    -> [(b, MixedAmount)] -> [(b, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ [(b, MixedAmount)]
    datedAmounts

      let units :: [(Decimal, Decimal, Decimal, Decimal)]
    units =
            [(Decimal, Decimal, Decimal, Decimal)]
    -> [(Decimal, Decimal, Decimal, Decimal)]
    forall a. HasCallStack => [a] -> [a]
    tail ([(Decimal, Decimal, Decimal, Decimal)]
     -> [(Decimal, Decimal, Decimal, Decimal)])
    -> [(Decimal, Decimal, Decimal, Decimal)]
    -> [(Decimal, Decimal, Decimal, Decimal)]
    forall a b. (a -> b) -> a -> b
    $
            ((Decimal, Decimal, Decimal, Decimal)
     -> (Day, Either MixedAmount MixedAmount)
     -> (Decimal, Decimal, Decimal, Decimal))
    -> (Decimal, Decimal, Decimal, Decimal)
    -> [(Day, Either MixedAmount MixedAmount)]
    -> [(Decimal, Decimal, Decimal, Decimal)]
    forall b a. (b -> a -> b) -> b -> [a] -> [b]
    scanl
              (\(Decimal
    _, Decimal
    _, Decimal
    unitPrice, Decimal
    unitBalance) (Day
    date, Either MixedAmount MixedAmount
    amt) ->
                 let valueOnDate :: Decimal
    valueOnDate = MixedAmount -> Decimal
    unMix (MixedAmount -> Decimal) -> MixedAmount -> Decimal
    forall a b. (a -> b) -> a -> b
    $ Day -> Day -> MixedAmount -> MixedAmount
    mixedAmountValue Day
    end Day
    date (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ [Transaction] -> Query -> MixedAmount
    total [Transaction]
    trans ([Query] -> Query
    And [Query
    investmentsQuery, DateSpan -> Query
    Date (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (EFDay -> Maybe EFDay
    forall a. a -> Maybe a
    Just (EFDay -> Maybe EFDay) -> EFDay -> Maybe EFDay
    forall a b. (a -> b) -> a -> b
    $ Day -> EFDay
    Exact Day
    date))])
                 in
                 case Either MixedAmount MixedAmount
    amt of
                   Right MixedAmount
    amt' ->
                     -- we are buying or selling
                     let unitsBoughtOrSold :: Decimal
    unitsBoughtOrSold = MixedAmount -> Decimal
    unMix MixedAmount
    amt' Decimal -> Decimal -> Decimal
    forall a. Fractional a => a -> a -> a
    / Decimal
    unitPrice
                     in (Decimal
    valueOnDate, Decimal
    unitsBoughtOrSold, Decimal
    unitPrice, Decimal
    unitBalance Decimal -> Decimal -> Decimal
    forall a. Num a => a -> a -> a
    + Decimal
    unitsBoughtOrSold)
                   Left MixedAmount
    pnl' ->
                     -- PnL change
                     let valueAfterDate :: Decimal
    valueAfterDate = Decimal
    valueOnDate Decimal -> Decimal -> Decimal
    forall a. Num a => a -> a -> a
    + MixedAmount -> Decimal
    unMix MixedAmount
    pnl'
                         unitPrice' :: Decimal
    unitPrice' = Decimal
    valueAfterDateDecimal -> Decimal -> Decimal
    forall a. Fractional a => a -> a -> a
    /Decimal
    unitBalance
                     in (Decimal
    valueOnDate, Decimal
    0, Decimal
    unitPrice', Decimal
    unitBalance))
              (Decimal
    0, Decimal
    0, Decimal
    initialUnitPrice, Decimal
    initialUnits)
              ([(Day, Either MixedAmount MixedAmount)]
     -> [(Decimal, Decimal, Decimal, Decimal)])
    -> [(Day, Either MixedAmount MixedAmount)]
    -> [(Decimal, Decimal, Decimal, Decimal)]
    forall a b. (a -> b) -> a -> b
    $ [Char]
    -> [(Day, Either MixedAmount MixedAmount)]
    -> [(Day, Either MixedAmount MixedAmount)]
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "changes" [(Day, Either MixedAmount MixedAmount)]
    changes

      let finalUnitBalance :: Decimal
    finalUnitBalance = if [(Decimal, Decimal, Decimal, Decimal)] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [(Decimal, Decimal, Decimal, Decimal)]
    units then Decimal
    initialUnits else let (Decimal
    _,Decimal
    _,Decimal
    _,Decimal
    u) = [(Decimal, Decimal, Decimal, Decimal)]
    -> (Decimal, Decimal, Decimal, Decimal)
    forall a. HasCallStack => [a] -> a
    last [(Decimal, Decimal, Decimal, Decimal)]
    units in Decimal
    u
          finalUnitPrice :: Decimal
    finalUnitPrice = if Decimal
    finalUnitBalance Decimal -> Decimal -> Bool
    forall a. Eq a => a -> a -> Bool
    == Decimal
    0 then
                             if [(Decimal, Decimal, Decimal, Decimal)] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [(Decimal, Decimal, Decimal, Decimal)]
    units then Decimal
    initialUnitPrice
                             else let (Decimal
    _,Decimal
    _,Decimal
    lastUnitPrice,Decimal
    _) = [(Decimal, Decimal, Decimal, Decimal)]
    -> (Decimal, Decimal, Decimal, Decimal)
    forall a. HasCallStack => [a] -> a
    last [(Decimal, Decimal, Decimal, Decimal)]
    units in Decimal
    lastUnitPrice
                           else (MixedAmount -> Decimal
    unMix MixedAmount
    valueAfter) Decimal -> Decimal -> Decimal
    forall a. Fractional a => a -> a -> a
    / Decimal
    finalUnitBalance
          -- Technically, totalTWR should be (100*(finalUnitPrice - initialUnitPrice) / initialUnitPrice), but initalUnitPrice is 100, so 100/100 == 1
          totalTWR :: Decimal
    totalTWR = Word8 -> Decimal -> Decimal
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo Word8
    2 (Decimal -> Decimal) -> Decimal -> Decimal
    forall a b. (a -> b) -> a -> b
    $ (Decimal
    finalUnitPrice Decimal -> Decimal -> Decimal
    forall a. Num a => a -> a -> a
    - Decimal
    initialUnitPrice)
          years :: Double
    years = Integer -> Double
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Day -> Day -> Integer
    diffDays Day
    end Day
    begin) Double -> Double -> Double
    forall a. Fractional a => a -> a -> a
    / Double
    365 :: Double
          annualizedTWR :: Double
    annualizedTWR = Double
    100Double -> Double -> Double
    forall a. Num a => a -> a -> a
    *((Double
    1Double -> Double -> Double
    forall a. Num a => a -> a -> a
    +(Decimal -> Double
    forall a b. (Real a, Fractional b) => a -> b
    realToFrac Decimal
    totalTWRDouble -> Double -> Double
    forall a. Fractional a => a -> a -> a
    /Double
    100))Double -> Double -> Double
    forall a. Floating a => a -> a -> a
    **(Double
    1Double -> Double -> Double
    forall a. Fractional a => a -> a -> a
    /Double
    years)Double -> Double -> Double
    forall a. Num a => a -> a -> a
    -Double
    1) :: Double

      Bool -> IO () -> IO ()
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    when Bool
    showCashFlow (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ do
        [Char] -> Text -> Text -> IO ()
    forall r. PrintfType r => [Char] -> r
    printf [Char]
    "\nTWR cash flow for %s - %s\n" (Day -> Text
    showDate Day
    begin) (Day -> Text
    showDate (Integer -> Day -> Day
    addDays (-Integer
    1) Day
    end))
        let ([Day]
    dates', [Either MixedAmount MixedAmount]
    amts) = [(Day, Either MixedAmount MixedAmount)]
    -> ([Day], [Either MixedAmount MixedAmount])
    forall a b. [(a, b)] -> ([a], [b])
    unzip [(Day, Either MixedAmount MixedAmount)]
    changes
            cashflows' :: [MixedAmount]
    cashflows' = (Either MixedAmount MixedAmount -> MixedAmount)
    -> [Either MixedAmount MixedAmount] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map (MixedAmount -> Either MixedAmount MixedAmount -> MixedAmount
    forall b a. b -> Either a b -> b
    fromRight MixedAmount
    nullmixedamt) [Either MixedAmount MixedAmount]
    amts
            pnls :: [MixedAmount]
    pnls = (Either MixedAmount MixedAmount -> MixedAmount)
    -> [Either MixedAmount MixedAmount] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map (MixedAmount -> Either MixedAmount MixedAmount -> MixedAmount
    forall a b. a -> Either a b -> a
    fromLeft MixedAmount
    nullmixedamt) [Either MixedAmount MixedAmount]
    amts
            ([Decimal]
    valuesOnDate,[Decimal]
    unitsBoughtOrSold', [Decimal]
    unitPrices', [Decimal]
    unitBalances') = [(Decimal, Decimal, Decimal, Decimal)]
    -> ([Decimal], [Decimal], [Decimal], [Decimal])
    forall a b c d. [(a, b, c, d)] -> ([a], [b], [c], [d])
    unzip4 [(Decimal, Decimal, Decimal, Decimal)]
    units
            add :: a -> [a] -> [a]
    add a
    x [a]
    lst = if Decimal
    valueBeforeDecimal -> Decimal -> Bool
    forall a. Eq a => a -> a -> Bool
    /=Decimal
    0 then a
    xa -> [a] -> [a]
    forall a. a -> [a] -> [a]
    :[a]
    lst else [a]
    lst
            dates :: [Day]
    dates = Day -> [Day] -> [Day]
    forall a. a -> [a] -> [a]
    add Day
    begin [Day]
    dates'
            cashflows :: [MixedAmount]
    cashflows = MixedAmount -> [MixedAmount] -> [MixedAmount]
    forall a. a -> [a] -> [a]
    add MixedAmount
    valueBeforeAmt [MixedAmount]
    cashflows'
            unitsBoughtOrSold :: [Decimal]
    unitsBoughtOrSold = Decimal -> [Decimal] -> [Decimal]
    forall a. a -> [a] -> [a]
    add Decimal
    initialUnits [Decimal]
    unitsBoughtOrSold'
            unitPrices :: [Decimal]
    unitPrices = Decimal -> [Decimal] -> [Decimal]
    forall a. a -> [a] -> [a]
    add Decimal
    initialUnitPrice [Decimal]
    unitPrices'
            unitBalances :: [Decimal]
    unitBalances = Decimal -> [Decimal] -> [Decimal]
    forall a. a -> [a] -> [a]
    add Decimal
    initialUnits [Decimal]
    unitBalances'

        Text -> IO ()
    TL.putStr (Text -> IO ()) -> Text -> IO ()
    forall a b. (a -> b) -> a -> b
    $ Bool
    -> (Text -> Text)
    -> (Text -> Text)
    -> ([Char] -> Text)
    -> Table Text Text [Char]
    -> Text
    forall rh ch a.
    Bool
    -> (rh -> Text)
    -> (ch -> Text)
    -> (a -> Text)
    -> Table rh ch a
    -> Text
    Tab.render Bool
    prettyTables Text -> Text
    forall a. a -> a
    id Text -> Text
    forall a. a -> a
    id [Char] -> Text
    T.pack
          (Header Text -> Header Text -> [[[Char]]] -> Table Text Text [Char]
    forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
    Table
           (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    NoLine ((Day -> Header Text) -> [Day] -> [Header Text]
    forall a b. (a -> b) -> [a] -> [b]
    map (Text -> Header Text
    forall h. h -> Header h
    Header (Text -> Header Text) -> (Day -> Text) -> Day -> Header Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> Text
    showDate) [Day]
    dates))
           (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    DoubleLine [ Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.SingleLine [Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    "Portfolio value", Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    "Unit balance"]
                             , Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.SingleLine [Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    "Pnl", Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    "Cashflow", Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    "Unit price", Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    "Units"]
                             , Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.SingleLine [Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    "New Unit Balance"]])
           [ [[Char]
    val, [Char]
    oldBalance, [Char]
    pnl', [Char]
    cashflow, [Char]
    prc, [Char]
    udelta, [Char]
    balance]
           | [Char]
    val <- (Decimal -> [Char]) -> [Decimal] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map Decimal -> [Char]
    showDecimal [Decimal]
    valuesOnDate
           | [Char]
    oldBalance <- (Decimal -> [Char]) -> [Decimal] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map Decimal -> [Char]
    showDecimal (Decimal
    0Decimal -> [Decimal] -> [Decimal]
    forall a. a -> [a] -> [a]
    :[Decimal]
    unitBalances)
           | [Char]
    balance <- (Decimal -> [Char]) -> [Decimal] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map Decimal -> [Char]
    showDecimal [Decimal]
    unitBalances
           | [Char]
    pnl' <- (MixedAmount -> [Char]) -> [MixedAmount] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map MixedAmount -> [Char]
    showMixedAmount [MixedAmount]
    pnls
           | [Char]
    cashflow <- (MixedAmount -> [Char]) -> [MixedAmount] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map MixedAmount -> [Char]
    showMixedAmount [MixedAmount]
    cashflows
           | [Char]
    prc <- (Decimal -> [Char]) -> [Decimal] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map Decimal -> [Char]
    showDecimal [Decimal]
    unitPrices
           | [Char]
    udelta <- (Decimal -> [Char]) -> [Decimal] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map Decimal -> [Char]
    showDecimal [Decimal]
    unitsBoughtOrSold ])

        [Char]
    -> [Char]
    -> [Char]
    -> [Char]
    -> [Char]
    -> Double
    -> Double
    -> IO ()
    forall r. PrintfType r => [Char] -> r
    printf [Char]
    "Final unit price: %s/%s units = %s\nTotal TWR: %s%%.\nPeriod: %.2f years.\nAnnualized TWR: %.2f%%\n\n"
          (MixedAmount -> [Char]
    showMixedAmount MixedAmount
    valueAfter) (Decimal -> [Char]
    showDecimal Decimal
    finalUnitBalance) (Decimal -> [Char]
    showDecimal Decimal
    finalUnitPrice) (Decimal -> [Char]
    showDecimal Decimal
    totalTWR) Double
    years Double
    annualizedTWR

      Double -> IO Double
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return Double
    annualizedTWR

    internalRateOfReturn :: Bool -> Bool -> OneSpan -> IO Double
    internalRateOfReturn Bool
    showCashFlow Bool
    prettyTables (OneSpan Day
    begin Day
    end MixedAmount
    valueBefore MixedAmount
    valueAfter [(Day, MixedAmount)]
    cashFlow [(Day, MixedAmount)]
    _pnl) = do
      let prefix :: (Day, MixedAmount)
    prefix = (Day
    begin, MixedAmount -> MixedAmount
    maNegate MixedAmount
    valueBefore)

          postfix :: (Day, MixedAmount)
    postfix = (Day
    end, MixedAmount
    valueAfter)

          totalCF :: [(Day, MixedAmount)]
    totalCF = ((Day, MixedAmount) -> Bool)
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (MixedAmount -> Bool
    maIsNonZero (MixedAmount -> Bool)
    -> ((Day, MixedAmount) -> MixedAmount)
    -> (Day, MixedAmount)
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Day, MixedAmount) -> MixedAmount
    forall a b. (a, b) -> b
    snd) ([(Day, MixedAmount)] -> [(Day, MixedAmount)])
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ (Day, MixedAmount)
    prefix (Day, MixedAmount) -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a. a -> [a] -> [a]
    : (((Day, MixedAmount) -> Day)
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall b a. Ord b => (a -> b) -> [a] -> [a]
    sortOn (Day, MixedAmount) -> Day
    forall a b. (a, b) -> a
    fst [(Day, MixedAmount)]
    cashFlow) [(Day, MixedAmount)]
    -> [(Day, MixedAmount)] -> [(Day, MixedAmount)]
    forall a. [a] -> [a] -> [a]
    ++ [(Day, MixedAmount)
    postfix]

      Bool -> IO () -> IO ()
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
    when Bool
    showCashFlow (IO () -> IO ()) -> IO () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ do
        [Char] -> Text -> Text -> IO ()
    forall r. PrintfType r => [Char] -> r
    printf [Char]
    "\nIRR cash flow for %s - %s\n" (Day -> Text
    showDate Day
    begin) (Day -> Text
    showDate (Integer -> Day -> Day
    addDays (-Integer
    1) Day
    end))
        let ([Day]
    dates, [MixedAmount]
    amts) = [(Day, MixedAmount)] -> ([Day], [MixedAmount])
    forall a b. [(a, b)] -> ([a], [b])
    unzip [(Day, MixedAmount)]
    totalCF
        Text -> IO ()
    TL.putStrLn (Text -> IO ()) -> Text -> IO ()
    forall a b. (a -> b) -> a -> b
    $ Bool
    -> (Text -> Text)
    -> (Text -> Text)
    -> (Text -> Text)
    -> Table Text Text Text
    -> Text
    forall rh ch a.
    Bool
    -> (rh -> Text)
    -> (ch -> Text)
    -> (a -> Text)
    -> Table rh ch a
    -> Text
    Tab.render Bool
    prettyTables Text -> Text
    forall a. a -> a
    id Text -> Text
    forall a. a -> a
    id Text -> Text
    forall a. a -> a
    id
          (Header Text -> Header Text -> [[Text]] -> Table Text Text Text
    forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
    Table
           (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.NoLine ((Day -> Header Text) -> [Day] -> [Header Text]
    forall a b. (a -> b) -> [a] -> [b]
    map (Text -> Header Text
    forall h. h -> Header h
    Header (Text -> Header Text) -> (Day -> Text) -> Day -> Header Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Day -> Text
    showDate) [Day]
    dates))
           (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.SingleLine [Text -> Header Text
    forall h. h -> Header h
    Header Text
    "Amount"])
           ((MixedAmount -> [Text]) -> [MixedAmount] -> [[Text]]
    forall a b. (a -> b) -> [a] -> [b]
    map ((Text -> [Text] -> [Text]
    forall a. a -> [a] -> [a]
    :[]) (Text -> [Text]) -> (MixedAmount -> Text) -> MixedAmount -> [Text]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [Char] -> Text
    T.pack ([Char] -> Text) -> (MixedAmount -> [Char]) -> MixedAmount -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . MixedAmount -> [Char]
    showMixedAmount) [MixedAmount]
    amts))

      -- 0% is always a solution, so require at least something here
      case [(Day, MixedAmount)]
    totalCF of
        [] -> Double -> IO Double
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return Double
    0
        [(Day, MixedAmount)]
    _ -> case RiddersParam
    -> (Double, Double) -> (Double -> Double) -> Root Double
    ridders (Int -> Tolerance -> RiddersParam
    RiddersParam Int
    100 (Double -> Tolerance
    AbsTol Double
    0.00001))
                          (Double
    0.000000000001,Double
    10000)
                          (Day -> [(Day, MixedAmount)] -> Double -> Double
    interestSum Day
    end [(Day, MixedAmount)]
    totalCF) of
            Root Double
    rate    -> Double -> IO Double
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return ((Double
    rateDouble -> Double -> Double
    forall a. Num a => a -> a -> a
    -Double
    1)Double -> Double -> Double
    forall a. Num a => a -> a -> a
    *Double
    100)
            Root Double
    NotBracketed -> [Char] -> IO Double
    forall a. [Char] -> a
    error' ([Char] -> IO Double) -> [Char] -> IO Double
    forall a b. (a -> b) -> a -> b
    $ [Char]
    "Error (NotBracketed): No solution for Internal Rate of Return (IRR).\n"
                            [Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++       [Char]
    "  Possible causes: IRR is huge (>1000000%), balance of investment becomes negative at some point in time."
            Root Double
    SearchFailed -> [Char] -> IO Double
    forall a. [Char] -> a
    error' ([Char] -> IO Double) -> [Char] -> IO Double
    forall a b. (a -> b) -> a -> b
    $ [Char]
    "Error (SearchFailed): Failed to find solution for Internal Rate of Return (IRR).\n"
                            [Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++       [Char]
    "  Either search does not converge to a solution, or converges too slowly."

    type CashFlow = [(Day, MixedAmount)]

    interestSum :: Day -> CashFlow -> Double -> Double
    interestSum :: Day -> [(Day, MixedAmount)] -> Double -> Double
    interestSum Day
    referenceDay [(Day, MixedAmount)]
    cf Double
    rate = [Double] -> Double
    forall a. Num a => [a] -> a
    forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
    sum ([Double] -> Double) -> [Double] -> Double
    forall a b. (a -> b) -> a -> b
    $ ((Day, MixedAmount) -> Double) -> [(Day, MixedAmount)] -> [Double]
    forall a b. (a -> b) -> [a] -> [b]
    map (Day, MixedAmount) -> Double
    go [(Day, MixedAmount)]
    cf
      where go :: (Day, MixedAmount) -> Double
    go (Day
    t,MixedAmount
    m) = Decimal -> Double
    forall a b. (Real a, Fractional b) => a -> b
    realToFrac (MixedAmount -> Decimal
    unMix MixedAmount
    m) Double -> Double -> Double
    forall a. Num a => a -> a -> a
    * Double
    rate Double -> Double -> Double
    forall a. Floating a => a -> a -> a
    ** (Integer -> Double
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Day
    referenceDay Day -> Day -> Integer
    `diffDays` Day
    t) Double -> Double -> Double
    forall a. Fractional a => a -> a -> a
    / Double
    365)

    calculateCashFlow :: WhichDate -> [Transaction] -> Query -> CashFlow
    calculateCashFlow :: WhichDate -> [Transaction] -> Query -> [(Day, MixedAmount)]
    calculateCashFlow WhichDate
    wd [Transaction]
    trans Query
    query =
      [ (WhichDate -> Posting -> Day
    postingDateOrDate2 WhichDate
    wd Posting
    p, Posting -> MixedAmount
    pamount Posting
    p) | Posting
    p <- (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Query -> Posting -> Bool
    matchesPosting Query
    query) ((Transaction -> [Posting]) -> [Transaction] -> [Posting]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap Transaction -> [Posting]
    realPostings [Transaction]
    trans), MixedAmount -> Bool
    maIsNonZero (Posting -> MixedAmount
    pamount Posting
    p) ]

    total :: [Transaction] -> Query -> MixedAmount
    total :: [Transaction] -> Query -> MixedAmount
    total [Transaction]
    trans Query
    query = [Posting] -> MixedAmount
    sumPostings ([Posting] -> MixedAmount)
    -> ([Posting] -> [Posting]) -> [Posting] -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Query -> Posting -> Bool
    matchesPosting Query
    query) ([Posting] -> MixedAmount) -> [Posting] -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (Transaction -> [Posting]) -> [Transaction] -> [Posting]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap Transaction -> [Posting]
    realPostings [Transaction]
    trans

    unMix :: MixedAmount -> Quantity
    unMix :: MixedAmount -> Decimal
    unMix MixedAmount
    a =
      case (MixedAmount -> Maybe Amount
    unifyMixedAmount (MixedAmount -> Maybe Amount) -> MixedAmount -> Maybe Amount
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> MixedAmount
    mixedAmountCost MixedAmount
    a) of
        Just Amount
    a' -> Amount -> Decimal
    aquantity Amount
    a'
        Maybe Amount
    Nothing -> [Char] -> Decimal
    forall a. [Char] -> a
    error' ([Char] -> Decimal) -> [Char] -> Decimal
    forall a b. (a -> b) -> a -> b
    $ [Char]
    "Amounts could not be converted to a single cost basis: " [Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++ [[Char]] -> [Char]
    forall a. Show a => a -> [Char]
    show ((Amount -> [Char]) -> [Amount] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> [Char]
    showAmount ([Amount] -> [[Char]]) -> [Amount] -> [[Char]]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Amount]
    amounts MixedAmount
    a) [Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++
                   [Char]
    "\nConsider using --value to force all costs to be in a single commodity." [Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++
                   [Char]
    "\nFor example, \"--cost --value=end,<commodity> --infer-market-prices\", where commodity is the one that was used to pay for the investment."

    -- Show Decimal rounded to two decimal places, unless it has less places already. This ensures that "2" won't be shown as "2.00"
    showDecimal :: Decimal -> String
    showDecimal :: Decimal -> [Char]
    showDecimal Decimal
    d = if Decimal
    d Decimal -> Decimal -> Bool
    forall a. Eq a => a -> a -> Bool
    == Decimal
    rounded then Decimal -> [Char]
    forall a. Show a => a -> [Char]
    show Decimal
    d else Decimal -> [Char]
    forall a. Show a => a -> [Char]
    show Decimal
    rounded
      where
        rounded :: Decimal
    rounded = Word8 -> Decimal -> Decimal
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo Word8
    2 Decimal
    d