Header menu logo FSharp.Finance.Personal

Amortisation Examples

Basic example #1

The following example shows a small personal loan of £1,500 taken out on 26 November 2022 and repaid on time in 5 monthly instalments, with a level payment of £456.88 and a final payment of £456.84:

#r "nuget:FSharp.Finance.Personal"

open FSharp.Finance.Personal
open Amortisation
open Calculation
open DateDay
open Scheduling
open UnitPeriod

let scheduleParameters =
    {
        EvaluationDate = Date(2023, 4, 1)
        StartDate = Date(2022, 11, 26)
        Principal = 1500_00L<Cent>
        ScheduleConfig = AutoGenerateSchedule {
            UnitPeriodConfig = Monthly(1, 2022, 11, 31)
            ScheduleLength = PaymentCount 5
        }
        PaymentConfig = {
            LevelPaymentOption = LowerFinalPayment
            ScheduledPaymentOption = AsScheduled
            Rounding = RoundUp
            Minimum = DeferOrWriteOff 50L<Cent>
            Timeout = 3<DurationDay>
        }
        FeeConfig = None
        ChargeConfig = None
        InterestConfig = {
            Method = Interest.Method.Simple
            StandardRate = Interest.Rate.Daily (Percent 0.8m)
            Cap = {
                TotalAmount = Amount.Percentage (Percent 100m, Restriction.NoLimit)
                DailyAmount = Amount.Percentage (Percent 0.8m, Restriction.NoLimit)
            }
            InitialGracePeriod = 3<DurationDay>
            PromotionalRates = [||]
            RateOnNegativeBalance = Interest.Rate.Zero
            Rounding = RoundDown
            AprMethod = Apr.CalculationMethod.UnitedKingdom 3
        }
    }

let actualPayments =
    Map [
        4<OffsetDay>, [| ActualPayment.quickConfirmed 456_88L<Cent> |]
        35<OffsetDay>, [| ActualPayment.quickConfirmed 456_88L<Cent> |]
        66<OffsetDay>, [| ActualPayment.quickConfirmed 456_88L<Cent> |]
        94<OffsetDay>, [| ActualPayment.quickConfirmed 456_88L<Cent> |]
        125<OffsetDay>, [| ActualPayment.quickConfirmed 456_84L<Cent> |]
    ]

let schedules =
    actualPayments
    |> Amortisation.generate scheduleParameters SettlementDay.NoSettlement false

schedules.AmortisationSchedule
{ ScheduleItems =
   map
     [(0, { OffsetDate = 2022-11-26
            Advances = [|150000L|]
            ScheduledPayment = <i>n/a<i>
            Window = 0
            PaymentDue = 0L
            ActualPayments = [||]
            GeneratedPayment = <i>n/a</i>
            NetEffect = 0L
            PaymentStatus = <i>none scheduled</i>
            BalanceStatus = open
            NewCharges = [||]
            ChargesPortion = 0L
            SimpleInterest = 0M
            NewInterest = 0M
            InterestPortion = 0L
            FeeRebate = 0L
            FeePortion = 0L
            PrincipalPortion = 0L
            ChargesBalance = 0L
            InterestBalance = 0M
            FeeBalance = 0L
            PrincipalBalance = 150000L
            SettlementFigure = 150000L
            FeeRebateIfSettled = 0L });
      (4, { OffsetDate = 2022-11-30
            Advances = [||]
            ScheduledPayment = <i>original</i> 456.88
            Window = 1
            PaymentDue = 45688L
            ActualPayments = [|<i>confirmed</i> 456.88|]
            GeneratedPayment = <i>n/a</i>
            NetEffect = 45688L
            PaymentStatus = <i>payment made</i>
            BalanceStatus = open
            NewCharges = [||]
            ChargesPortion = 0L
            SimpleInterest = 4800M
            NewInterest = 4800M
            InterestPortion = 4800L
            FeeRebate = 0L
            FeePortion = 0L
            PrincipalPortion = 40888L
            ChargesBalance = 0L
            InterestBalance = 0M
            FeeBalance = 0L
            PrincipalBalance = 109112L
            SettlementFigure = 109112L
            FeeRebateIfSettled = 0L });
      (35, { OffsetDate = 2022-12-31
             Advances = [||]
             ScheduledPayment = <i>original</i> 456.88
             Window = 2
             PaymentDue = 45688L
             ActualPayments = [|<i>confirmed</i> 456.88|]
             GeneratedPayment = <i>n/a</i>
             NetEffect = 45688L
             PaymentStatus = <i>payment made</i>
             BalanceStatus = open
             NewCharges = [||]
             ChargesPortion = 0L
             SimpleInterest = 27059.776M
             NewInterest = 27059.776M
             InterestPortion = 27059L
             FeeRebate = 0L
             FeePortion = 0L
             PrincipalPortion = 18629L
             ChargesBalance = 0L
             InterestBalance = 0M
             FeeBalance = 0L
             PrincipalBalance = 90483L
             SettlementFigure = 90483L
             FeeRebateIfSettled = 0L });
      (66, { OffsetDate = 2023-01-31
             Advances = [||]
             ScheduledPayment = <i>original</i> 456.88
             Window = 3
             PaymentDue = 45688L
             ActualPayments = [|<i>confirmed</i> 456.88|]
             GeneratedPayment = <i>n/a</i>
             NetEffect = 45688L
             PaymentStatus = <i>payment made</i>
             BalanceStatus = open
             NewCharges = [||]
             ChargesPortion = 0L
             SimpleInterest = 22439.784M
             NewInterest = 22439.784M
             InterestPortion = 22439L
             FeeRebate = 0L
             FeePortion = 0L
             PrincipalPortion = 23249L
             ChargesBalance = 0L
             InterestBalance = 0M
             FeeBalance = 0L
             PrincipalBalance = 67234L
             SettlementFigure = 67234L
             FeeRebateIfSettled = 0L });
      (94, { OffsetDate = 2023-02-28
             Advances = [||]
             ScheduledPayment = <i>original</i> 456.88
             Window = 4
             PaymentDue = 45688L
             ActualPayments = [|<i>confirmed</i> 456.88|]
             GeneratedPayment = <i>n/a</i>
             NetEffect = 45688L
             PaymentStatus = <i>payment made</i>
             BalanceStatus = open
             NewCharges = [||]
             ChargesPortion = 0L
             SimpleInterest = 15060.416M
             NewInterest = 15060.416M
             InterestPortion = 15060L
             FeeRebate = 0L
             FeePortion = 0L
             PrincipalPortion = 30628L
             ChargesBalance = 0L
             InterestBalance = 0M
             FeeBalance = 0L
             PrincipalBalance = 36606L
             SettlementFigure = 36606L
             FeeRebateIfSettled = 0L });
      (125, { OffsetDate = 2023-03-31
              Advances = [||]
              ScheduledPayment = <i>original</i> 456.84
              Window = 5
              PaymentDue = 45684L
              ActualPayments = [|<i>confirmed</i> 456.84|]
              GeneratedPayment = <i>n/a</i>
              NetEffect = 45684L
              PaymentStatus = <i>payment made</i>
              BalanceStatus = closed
              NewCharges = [||]
              ChargesPortion = 0L
              SimpleInterest = 9078.288M
              NewInterest = 9078.288M
              InterestPortion = 9078L
              FeeRebate = 0L
              FeePortion = 0L
              PrincipalPortion = 36606L
              ChargesBalance = 0L
              InterestBalance = 0M
              FeeBalance = 0L
              PrincipalBalance = 0L
              SettlementFigure = 0L
              FeeRebateIfSettled = 0L })]
  FinalStats = { RequiredScheduledPaymentCount = 5
                 LastRequiredScheduledPaymentDay = ValueSome 125
                 FinalActualPaymentCount = 5
                 LastActualPaymentDay = ValueSome 125
                 FinalCostToBorrowingRatio = 52.29 %
                 EffectiveInterestRate = 0.4183253333 % per day
                 SettlementFigure = ValueNone
                 FinalBalanceStatus = closed} }

It is possible to format the Items property as an HTML table:

let html = Schedule.toHtmlTable schedules.AmortisationSchedule

$"""<div class="schedule">{html}</div>"""
Day Datestamp Advances Scheduled payment Window Payment due Actual payments Generated payment Net effect Payment status Balance status Simple interest New interest New charges Principal portion Fee portion Interest portion Charges portion Fee rebate Principal balance Fee balance Interest balance Charges balance Settlement figure Fee rebate if settled
0 2022-11-26 1,500.00 n/a 0 0.00 n/a n/a 0.00 none scheduled open 0.0000 0.0000 n/a 0.00 0.00 0.00 0.00 0.00 1,500.00 0.00 0.0000 0.00 1,500.00 0.00
4 2022-11-30 n/a original 456.88 1 456.88 confirmed 456.88 n/a 456.88 payment made open 48.0000 48.0000 n/a 408.88 0.00 48.00 0.00 0.00 1,091.12 0.00 0.0000 0.00 1,091.12 0.00
35 2022-12-31 n/a original 456.88 2 456.88 confirmed 456.88 n/a 456.88 payment made open 270.5978 270.5978 n/a 186.29 0.00 270.59 0.00 0.00 904.83 0.00 0.0000 0.00 904.83 0.00
66 2023-01-31 n/a original 456.88 3 456.88 confirmed 456.88 n/a 456.88 payment made open 224.3978 224.3978 n/a 232.49 0.00 224.39 0.00 0.00 672.34 0.00 0.0000 0.00 672.34 0.00
94 2023-02-28 n/a original 456.88 4 456.88 confirmed 456.88 n/a 456.88 payment made open 150.6042 150.6042 n/a 306.28 0.00 150.60 0.00 0.00 366.06 0.00 0.0000 0.00 366.06 0.00
125 2023-03-31 n/a original 456.84 5 456.84 confirmed 456.84 n/a 456.84 payment made closed 90.7829 90.7829 n/a 366.06 0.00 90.78 0.00 0.00 0.00 0.00 0.0000 0.00 0.00 0.00
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.Finance
namespace FSharp.Finance.Personal
module Amortisation from FSharp.Finance.Personal
<summary> calculating the principal balance over time, taking into account the effects of charges, interest and fee </summary>
module Calculation from FSharp.Finance.Personal
<summary> convenience functions and options to help with calculations </summary>
module DateDay from FSharp.Finance.Personal
<summary> a .NET Framework polyfill equivalent to the DateOnly structure in .NET Core </summary>
module Scheduling from FSharp.Finance.Personal
<summary> functions for generating a regular payment schedule, with payment amounts, interest and APR </summary>
module UnitPeriod from FSharp.Finance.Personal
<summary> an unambiguous way to represent regular date intervals and generate schedules based on them note: unit-period definitions are based on US federal legislation but the definitions are universally applicable </summary>
val scheduleParameters: Parameters
Multiple items
[<Struct>] type Date = new: year: int * month: int * day: int -> Date val Year: int val Month: int val Day: int member AddDays: i: int -> Date member AddMonths: i: int -> Date member AddYears: i: int -> Date member ToDateTime: unit -> DateTime static member (-) : d1: Date * d2: Date -> TimeSpan static member DaysInMonth: year: int * month: int -> int ...
<summary> the date at the customer's location - ensure any time-zone conversion is performed before using this - as all calculations are date-only with no time component, summer time or other such time artefacts </summary>

--------------------
Date ()
new: year: int * month: int * day: int -> Date
Multiple items
module Cent from FSharp.Finance.Personal.Calculation
<summary> utility functions for base currency unit values </summary>

--------------------
[<Measure>] type Cent
<summary> the base unit of a currency (cent, penny, øre etc.) </summary>
Multiple items
module ScheduleConfig from FSharp.Finance.Personal.Scheduling
<summary> whether a payment plan is generated according to a regular schedule or is an irregular array of payments </summary>

--------------------
[<Struct>] type ScheduleConfig = | AutoGenerateSchedule of AutoGenerateSchedule: AutoGenerateSchedule | FixedSchedules of FixedSchedules: FixedSchedule array | CustomSchedule of CustomSchedule: Map<int<OffsetDay>,ScheduledPayment>
<summary> whether a payment plan is generated according to a regular schedule or is an irregular array of payments </summary>
Multiple items
union case ScheduleConfig.AutoGenerateSchedule: AutoGenerateSchedule: AutoGenerateSchedule -> ScheduleConfig
<summary> a schedule based on a unit-period config with a specific number of payments with an auto-calculated amount, optionally limited to a maximum duration </summary>

--------------------
[<Struct>] type AutoGenerateSchedule = { UnitPeriodConfig: Config ScheduleLength: ScheduleLength }
<summary> a regular schedule based on a unit-period config with a specific number of payments with an auto-calculated amount </summary>
union case Config.Monthly: MonthMultiple: int * Year: int * Month: int * Day: int -> Config
<summary> (multi-)monthly: every n months starting on the date given by year, month and day, which tracks month-end (see config) </summary>
[<Struct>] type ScheduleLength = | PaymentCount of Payments: int | MaxDuration of Days: int<DurationDay> member Html: string with get
<summary> defines the length of a payment schedule, either by the number of payments or by the maximum duration </summary>
union case ScheduleLength.PaymentCount: Payments: int -> ScheduleLength
Multiple items
module PaymentConfig from FSharp.Finance.Personal.Scheduling
<summary> how to treat scheduled payments </summary>

--------------------
type PaymentConfig = { LevelPaymentOption: LevelPaymentOption ScheduledPaymentOption: ScheduledPaymentOption Rounding: Rounding Minimum: MinimumPayment Timeout: int<DurationDay> }
<summary> how to treat scheduled payments </summary>
Multiple items
module LevelPaymentOption from FSharp.Finance.Personal.Scheduling
<summary> when calculating the level payments, whether the final payment should be lower or higher than the level payment </summary>

--------------------
[<Struct>] type LevelPaymentOption = | LowerFinalPayment | SimilarFinalPayment | HigherFinalPayment member Html: string with get
<summary> when calculating the level payments, whether the final payment should be lower or higher than the level payment </summary>
union case LevelPaymentOption.LowerFinalPayment: LevelPaymentOption
<summary> the final payment must be lower than the level payment </summary>
[<Struct>] type ScheduledPaymentOption = | AsScheduled | AddChargesAndInterest member Html: string with get
<summary> whether to stick to scheduled payment amounts or add charges and interest to them </summary>
union case ScheduledPaymentOption.AsScheduled: ScheduledPaymentOption
<summary> keep to the scheduled payment amounts even if this results in an open balance </summary>
Multiple items
module Rounding from FSharp.Finance.Personal.Calculation
<summary> the type of rounding, specifying midpoint-rounding where necessary </summary>

--------------------
[<Struct>] type Rounding = | NoRounding | RoundUp | RoundDown | RoundWith of MidpointRounding member Html: string with get
<summary> the type of rounding, specifying midpoint-rounding where necessary </summary>
union case Rounding.RoundUp: Rounding
<summary> round up to the specified precision (= ceiling) </summary>
union case MinimumPayment.DeferOrWriteOff: DeferOrWriteOff: int64<Cent> -> MinimumPayment
<summary> add the payment due to the next payment or close the balance if the final payment </summary>
[<Measure>] type DurationDay
<summary> a duration of a number of days </summary>
union case Option.None: Option<'T>
module Interest from FSharp.Finance.Personal
<summary> methods for calculating interest and unambiguously expressing interest rates, as well as enforcing regulatory caps on interest chargeable </summary>
[<Struct>] type Method = | Simple | AddOn member Html: string with get
<summary> the method used to calculate the interest </summary>
union case Interest.Method.Simple: Interest.Method
<summary> simple interest method, where interest is based on the principal balance and the number of days outstanding </summary>
Multiple items
module Rate from FSharp.Finance.Personal.Interest

--------------------
[<Struct>] type Rate = | Zero | Annual of Annual: Percent | Daily of Daily: Percent member Html: string with get
<summary> the interest rate expressed as either an annual or a daily rate </summary>
union case Interest.Rate.Daily: Daily: Percent -> Interest.Rate
<summary> the daily interest rate, or the annual interest rate divided by 365 </summary>
Multiple items
union case Percent.Percent: decimal -> Percent

--------------------
module Percent from FSharp.Finance.Personal.Calculation
<summary> utility functions for percent values </summary>

--------------------
[<Struct>] type Percent = | Percent of decimal member Html: string with get
<summary> a percentage, e.g. 42%, as opposed to its decimal representation 0.42m </summary>
Multiple items
module Amount from FSharp.Finance.Personal.Calculation
<summary> an amount specified either as a simple amount or as a percentage of another amount, optionally restricted to lower and/or upper limits </summary>

--------------------
[<Struct>] type Amount = | Percentage of Percentage: Percent * Restriction: Restriction | Simple of Simple: int64<Cent> | Unlimited member Html: string with get
<summary> an amount specified either as a simple amount or as a percentage of another amount, optionally restricted to lower and/or upper limits </summary>
union case Amount.Percentage: Percentage: Percent * Restriction: Restriction -> Amount
<summary> a percentage of the principal, optionally restricted </summary>
Multiple items
module Restriction from FSharp.Finance.Personal.Calculation
<summary> the type of restriction placed on a possible value </summary>

--------------------
[<Struct>] type Restriction = | NoLimit | LowerLimit of LowerLimit: int64<Cent> | UpperLimit of UpperLimit: int64<Cent> | WithinRange of MinValue: int64<Cent> * MaxValue: int64<Cent> member Html: string with get
<summary> the type of restriction placed on a possible value </summary>
union case Restriction.NoLimit: Restriction
<summary> does not constrain values at all </summary>
union case Interest.Rate.Zero: Interest.Rate
<summary> a zero rate </summary>
union case Rounding.RoundDown: Rounding
<summary> round down to the specified precision (= floor) </summary>
module Apr from FSharp.Finance.Personal
<summary> calculating the APR according to various country-specific regulations </summary>
[<Struct>] type CalculationMethod = | UnitedKingdom of UkPrecision: int | UsActuarial of UsPrecision: int | UnitedStatesRule member Html: string with get
<summary> the calculation method used to determine the APR </summary>
union case Apr.CalculationMethod.UnitedKingdom: UkPrecision: int -> Apr.CalculationMethod
<summary> calculates the APR according to UK FCA rules to the stated decimal precision (note that this is two places more than the percent precision) </summary>
val actualPayments: Map<int<OffsetDay>,ActualPayment array>
Multiple items
module Map from FSharp.Finance.Personal.Calculation
<summary> functions for working with maps </summary>

--------------------
module Map from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> = interface IReadOnlyDictionary<'Key,'Value> interface IReadOnlyCollection<KeyValuePair<'Key,'Value>> interface IEnumerable interface IStructuralEquatable interface IComparable interface IEnumerable<KeyValuePair<'Key,'Value>> interface ICollection<KeyValuePair<'Key,'Value>> interface IDictionary<'Key,'Value> new: elements: ('Key * 'Value) seq -> Map<'Key,'Value> member Add: key: 'Key * value: 'Value -> Map<'Key,'Value> ...

--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
Multiple items
module OffsetDay from FSharp.Finance.Personal.DateDay
<summary> functions for converting offset days to and from dates </summary>

--------------------
[<Measure>] type OffsetDay
<summary> the offset of a date from the start date, in days </summary>
Multiple items
module ActualPayment from FSharp.Finance.Personal.Scheduling
<summary> an actual payment made by the customer, optionally including metadata such as bank references etc. </summary>

--------------------
type ActualPayment = { ActualPaymentStatus: ActualPaymentStatus Metadata: Map<string,obj> } member Html: string with get
<summary> an actual payment made by the customer, optionally including metadata such as bank references etc. </summary>
val quickConfirmed: amount: int64<Cent> -> ActualPayment
<summary> a quick convenient method to create a confirmed actual payment </summary>
val schedules: GenerationResult
val generate: sp: Parameters -> settlementDay: SettlementDay -> trimEnd: bool -> actualPayments: Map<int<OffsetDay>,ActualPayment array> -> GenerationResult
<summary> generates an amortisation schedule and final statistics </summary>
[<Struct>] type SettlementDay = | SettlementOn of SettlementDay: int<OffsetDay> | SettlementOnEvaluationDay | NoSettlement
<summary> the intended day on which to quote a settlement </summary>
union case SettlementDay.NoSettlement: SettlementDay
<summary> no settlement figure is required </summary>
GenerationResult.AmortisationSchedule: Schedule
val html: string
Multiple items
module Schedule from FSharp.Finance.Personal.Amortisation
<summary> a schedule showing the amortisation, itemising the effects of payments and calculating balances for each item, and producing some final statistics resulting from the calculations </summary>

--------------------
[<Struct>] type Schedule = { ScheduleItems: Map<int<OffsetDay>,ScheduleItem> FinalStats: FinalStats }
<summary> a schedule showing the amortisation, itemising the effects of payments and calculating balances for each item, and producing some final statistics resulting from the calculations </summary>
val toHtmlTable: schedule: Schedule -> string
<summary> formats the schedule items as an HTML table (stats can be rendered separately) </summary>

Type something to start searching.