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 AppliedPayment
open Calculation
open DateDay
open Scheduling
open UnitPeriod

let parameters: Parameters = {
    Basic = {
        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
            Rounding = RoundUp
        }
        FeeConfig = ValueNone
        InterestConfig = {
            Method = Interest.Method.Actuarial
            StandardRate = Interest.Rate.Daily(Percent 0.8m)
            Cap = {
                TotalAmount = Amount.Percentage(Percent 100m, Restriction.NoLimit)
                DailyAmount = Amount.Percentage(Percent 0.8m, Restriction.NoLimit)
            }
            Rounding = RoundDown
            AprMethod = Apr.CalculationMethod.UnitedKingdom 3
        }
    }
    Advanced = {
        PaymentConfig = {
            ScheduledPaymentOption = AsScheduled
            Minimum = DeferOrWriteOff 50L<Cent>
            Timeout = 3<DurationDay>
        }
        FeeConfig = ValueNone
        ChargeConfig = None
        InterestConfig = {
            InitialGracePeriod = 3<DurationDay>
            PromotionalRates = [||]
            RateOnNegativeBalance = Interest.Rate.Zero
        }
        SettlementDay = SettlementDay.NoSettlement
        TrimEnd = false
    }
}

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 |> amortise parameters

schedules.AmortisationSchedule
{ ScheduleItems =
   map
     [(0, { OffsetDayType = OffsetDay
            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
            ActuarialInterest = 0M
            NewInterest = 0M
            InterestPortion = 0L
            FeeRebate = 0L
            FeePortion = 0L
            PrincipalPortion = 0L
            ChargesBalance = 0L
            InterestBalance = 0M
            FeeBalance = 0L
            PrincipalBalance = 150000L
            SettlementFigure = 150000L
            FeeRebateIfSettled = 0L });
      (4, { OffsetDayType = OffsetDay
            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
            ActuarialInterest = 4800M
            NewInterest = 4800M
            InterestPortion = 4800L
            FeeRebate = 0L
            FeePortion = 0L
            PrincipalPortion = 40888L
            ChargesBalance = 0L
            InterestBalance = 0M
            FeeBalance = 0L
            PrincipalBalance = 109112L
            SettlementFigure = 109112L
            FeeRebateIfSettled = 0L });
      (35, { OffsetDayType = OffsetDay
             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
             ActuarialInterest = 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, { OffsetDayType = OffsetDay
             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
             ActuarialInterest = 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, { OffsetDayType = OffsetDay
             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
             ActuarialInterest = 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, { OffsetDayType = OffsetDay
              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
              ActuarialInterest = 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 });
      (126, { OffsetDayType = EvaluationDay
              OffsetDate = 2023-04-01
              Advances = [||]
              ScheduledPayment = <i>n/a<i>
              Window = 5
              PaymentDue = 0L
              ActualPayments = [||]
              GeneratedPayment = <i>n/a</i>
              NetEffect = 0L
              PaymentStatus = <i>information only</i>
              BalanceStatus = closed
              NewCharges = [||]
              ChargesPortion = 0L
              ActuarialInterest = 0M
              NewInterest = 0M
              InterestPortion = 0L
              FeeRebate = 0L
              FeePortion = 0L
              PrincipalPortion = 0L
              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.415005291 % per day
                 SettlementFigure = ValueNone
                 FinalBalanceStatus = closed} }

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

let html = Schedule.toHtmlTable parameters schedules.AmortisationSchedule

$"""<div class="schedule">{html}</div>"""
Day Datestamp Advances Scheduled payment Window Payment due Actual payments Net effect Payment status Balance status Actuarial interest New interest Interest portion Principal portion Interest balance Principal balance Settlement figure
0 2022-11-26 1,500.00 n/a 0 0.00 n/a 0.00 none scheduled open 0.0000 0.0000 0.00 0.00 0.0000 1,500.00 1,500.00
4 2022-11-30 n/a original 456.88 1 456.88 confirmed 456.88 456.88 payment made open 48.0000 48.0000 48.00 408.88 0.0000 1,091.12 1,091.12
35 2022-12-31 n/a original 456.88 2 456.88 confirmed 456.88 456.88 payment made open 270.5978 270.5978 270.59 186.29 0.0000 904.83 904.83
66 2023-01-31 n/a original 456.88 3 456.88 confirmed 456.88 456.88 payment made open 224.3978 224.3978 224.39 232.49 0.0000 672.34 672.34
94 2023-02-28 n/a original 456.88 4 456.88 confirmed 456.88 456.88 payment made open 150.6042 150.6042 150.60 306.28 0.0000 366.06 366.06
125 2023-03-31 n/a original 456.84 5 456.84 confirmed 456.84 456.84 payment made closed 90.7829 90.7829 90.78 366.06 0.0000 0.00 0.00
★ 126 2023-04-01 n/a n/a 5 0.00 n/a 0.00 information only closed 0.0000 0.0000 0.00 0.00 0.0000 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 AppliedPayment from FSharp.Finance.Personal
<summary> functions for handling received payments and calculating interest and/or charges where necessary </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 parameters: Parameters
type Parameters = { Basic: BasicParameters Advanced: AdvancedParameters }
<summary> basic schedule generation parameters and advanced parameters for amortisation </summary>
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: PaymentMap
<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 StartDate: Date * 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 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>
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 ValueOption.ValueNone: ValueOption<'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 = | Actuarial | AddOn member Html: string with get
<summary> the method used to calculate the interest </summary>
union case Interest.Method.Actuarial: Interest.Method
<summary> actuarial 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 Percent | Daily of 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: 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 Percent * Restriction | Simple of 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: Percent * 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 int64<Cent> | UpperLimit of 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 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 = | EuropeanUnion of EuPrecision: int | 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>
[<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>
union case MinimumPayment.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>
union case Interest.Rate.Zero: Interest.Rate
<summary> a zero rate </summary>
[<Struct>] type SettlementDay = | SettlementOnEvaluationDay | NoSettlement member Html: string with get
<summary> the intended day on which to quote a settlement </summary>
union case SettlementDay.NoSettlement: SettlementDay
<summary> no settlement figure is required </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 amortise: p: Parameters -> actualPayments: Map<int<OffsetDay>,ActualPayment array> -> GenerationResult
<summary> generates an amortisation schedule and final statistics </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: p: Parameters -> schedule: Schedule -> string
<summary> formats the schedule items as an HTML table (stats can be rendered separately) </summary>

Type something to start searching.