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

let scheduleParameters =
    {
        AsOfDate = Date(2023, 4, 1)
        StartDate = Date(2022, 11, 26)
        Principal = 1500_00L<Cent>
        ScheduleConfig = AutoGenerateSchedule {
            UnitPeriodConfig = UnitPeriod.Monthly(1, 2022, 11, 31)
            PaymentCount = 5
            MaxDuration = Duration.Unlimited
        }
        PaymentConfig = {
            ScheduledPaymentOption = AsScheduled
            CloseBalanceOption = LeaveOpenBalance
            PaymentRounding = RoundUp
            MinimumPayment = DeferOrWriteOff 50L<Cent>
            PaymentTimeout = 3<DurationDay>
        }
        FeeConfig = Fee.Config.initialRecommended
        ChargeConfig = {
            ChargeTypes = [| Charge.LatePayment (Amount.Simple 10_00L<Cent>) |]
            Rounding = RoundDown
            ChargeHolidays = [||]
            ChargeGrouping = Charge.ChargeGrouping.OneChargeTypePerDay
            LatePaymentGracePeriod = 0<DurationDay>
        }
        InterestConfig = {
            Method = Interest.Method.Simple
            StandardRate = Interest.Rate.Daily (Percent 0.8m)
            Cap = {
                TotalAmount = ValueSome <| Amount.Percentage (Percent 100m, Restriction.NoLimit, RoundDown)
                DailyAmount = ValueSome <| Amount.Percentage (Percent 0.8m, Restriction.NoLimit, NoRounding)
            }
            InitialGracePeriod = 3<DurationDay>
            PromotionalRates = [||]
            RateOnNegativeBalance = Interest.Rate.Zero
            AprMethod = Apr.CalculationMethod.UnitedKingdom 3
            InterestRounding = RoundDown
        }
    }

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 amortisationSchedule =
    actualPayments
    |> Amortisation.generate scheduleParameters ValueNone false

amortisationSchedule
{ ScheduleItems =
   map
     [(0, { OffsetDate = 2022-11-26
            Advances = [|150000L|]
            ScheduledPayment = 
            Window = 0
            PaymentDue = 0L
            ActualPayments = [||]
            GeneratedPayment = 
            NetEffect = 0L
            PaymentStatus = NoneScheduled
            BalanceStatus = OpenBalance
            NewCharges = [||]
            ChargesPortion = 0L
            OriginalSimpleInterest = 0L
            ContractualInterest = 0M
            SimpleInterest = 0M
            NewInterest = 0M
            InterestPortion = 0L
            FeesRefund = 0L
            FeesPortion = 0L
            PrincipalPortion = 0L
            ChargesBalance = 0L
            InterestBalance = 0M
            FeesBalance = 0L
            PrincipalBalance = 150000L
            SettlementFigure = ValueSome 150000L
            FeesRefundIfSettled = 0L });
      (4, { OffsetDate = 2022-11-30
            Advances = [||]
            ScheduledPayment = original 456.88
            Window = 1
            PaymentDue = 45688L
            ActualPayments = [|{ ActualPaymentStatus = Confirmed 45688L
                                 Metadata = map [] }|]
            GeneratedPayment = 
            NetEffect = 45688L
            PaymentStatus = PaymentMade
            BalanceStatus = OpenBalance
            NewCharges = [||]
            ChargesPortion = 0L
            OriginalSimpleInterest = 0L
            ContractualInterest = 0M
            SimpleInterest = 4800M
            NewInterest = 4800M
            InterestPortion = 4800L
            FeesRefund = 0L
            FeesPortion = 0L
            PrincipalPortion = 40888L
            ChargesBalance = 0L
            InterestBalance = 0M
            FeesBalance = 0L
            PrincipalBalance = 109112L
            SettlementFigure = ValueSome 109112L
            FeesRefundIfSettled = 0L });
      (35, { OffsetDate = 2022-12-31
             Advances = [||]
             ScheduledPayment = original 456.88
             Window = 2
             PaymentDue = 45688L
             ActualPayments = [|{ ActualPaymentStatus = Confirmed 45688L
                                  Metadata = map [] }|]
             GeneratedPayment = 
             NetEffect = 45688L
             PaymentStatus = PaymentMade
             BalanceStatus = OpenBalance
             NewCharges = [||]
             ChargesPortion = 0L
             OriginalSimpleInterest = 0L
             ContractualInterest = 0M
             SimpleInterest = 27059.776M
             NewInterest = 27059.776M
             InterestPortion = 27059L
             FeesRefund = 0L
             FeesPortion = 0L
             PrincipalPortion = 18629L
             ChargesBalance = 0L
             InterestBalance = 0M
             FeesBalance = 0L
             PrincipalBalance = 90483L
             SettlementFigure = ValueSome 90483L
             FeesRefundIfSettled = 0L });
      (66, { OffsetDate = 2023-01-31
             Advances = [||]
             ScheduledPayment = original 456.88
             Window = 3
             PaymentDue = 45688L
             ActualPayments = [|{ ActualPaymentStatus = Confirmed 45688L
                                  Metadata = map [] }|]
             GeneratedPayment = 
             NetEffect = 45688L
             PaymentStatus = PaymentMade
             BalanceStatus = OpenBalance
             NewCharges = [||]
             ChargesPortion = 0L
             OriginalSimpleInterest = 0L
             ContractualInterest = 0M
             SimpleInterest = 22439.784M
             NewInterest = 22439.784M
             InterestPortion = 22439L
             FeesRefund = 0L
             FeesPortion = 0L
             PrincipalPortion = 23249L
             ChargesBalance = 0L
             InterestBalance = 0M
             FeesBalance = 0L
             PrincipalBalance = 67234L
             SettlementFigure = ValueSome 67234L
             FeesRefundIfSettled = 0L });
      (94, { OffsetDate = 2023-02-28
             Advances = [||]
             ScheduledPayment = original 456.88
             Window = 4
             PaymentDue = 45688L
             ActualPayments = [|{ ActualPaymentStatus = Confirmed 45688L
                                  Metadata = map [] }|]
             GeneratedPayment = 
             NetEffect = 45688L
             PaymentStatus = PaymentMade
             BalanceStatus = OpenBalance
             NewCharges = [||]
             ChargesPortion = 0L
             OriginalSimpleInterest = 0L
             ContractualInterest = 0M
             SimpleInterest = 15060.416M
             NewInterest = 15060.416M
             InterestPortion = 15060L
             FeesRefund = 0L
             FeesPortion = 0L
             PrincipalPortion = 30628L
             ChargesBalance = 0L
             InterestBalance = 0M
             FeesBalance = 0L
             PrincipalBalance = 36606L
             SettlementFigure = ValueSome 36606L
             FeesRefundIfSettled = 0L });
      (125, { OffsetDate = 2023-03-31
              Advances = [||]
              ScheduledPayment = original 456.84
              Window = 5
              PaymentDue = 45684L
              ActualPayments = [|{ ActualPaymentStatus = Confirmed 45684L
                                   Metadata = map [] }|]
              GeneratedPayment = 
              NetEffect = 45684L
              PaymentStatus = PaymentMade
              BalanceStatus = ClosedBalance
              NewCharges = [||]
              ChargesPortion = 0L
              OriginalSimpleInterest = 0L
              ContractualInterest = 0M
              SimpleInterest = 9078.288M
              NewInterest = 9078.288M
              InterestPortion = 9078L
              FeesRefund = 0L
              FeesPortion = 0L
              PrincipalPortion = 36606L
              ChargesBalance = 0L
              InterestBalance = 0M
              FeesBalance = 0L
              PrincipalBalance = 0L
              SettlementFigure = ValueSome 0L
              FeesRefundIfSettled = 0L })]
  FinalScheduledPaymentDay = 125
  FinalScheduledPaymentCount = 5
  FinalActualPaymentCount = 5
  FinalApr = ValueNone
  FinalCostToBorrowingRatio = Percent 52.29M
  EffectiveInterestRate = Daily (Percent 0.0041832533333333333333333333M) }

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

let html = amortisationSchedule.ScheduleItems |> Formatting.generateHtmlFromMap [||]

$"""<div style="overflow-x: auto;">{html}</div>"""
DayOffset DateAdvancesScheduled PaymentWindowPayment DueActual PaymentsGenerated PaymentNet EffectPayment StatusBalance StatusNew ChargesCharges PortionOriginal Simple InterestContractual InterestSimple InterestNew InterestInterest PortionFees RefundFees PortionPrincipal PortionCharges BalanceInterest BalanceFees BalancePrincipal BalanceSettlement FigureFees Refund If Settled
02022-11-261,500.0000.000.00  open balance0.000.000.00000.00000.00000.000.000.000.000.000.00000.001,500.00 1,500.000.00
42022-11-30original 456.881456.88 confirmed 456.88 456.88 payment made open balance0.000.000.000048.000048.000048.000.000.00408.880.000.00000.001,091.12 1,091.120.00
352022-12-31original 456.882456.88 confirmed 456.88 456.88 payment made open balance0.000.000.0000270.5978270.5978270.590.000.00186.290.000.00000.00904.83 904.830.00
662023-01-31original 456.883456.88 confirmed 456.88 456.88 payment made open balance0.000.000.0000224.3978224.3978224.390.000.00232.490.000.00000.00672.34 672.340.00
942023-02-28original 456.884456.88 confirmed 456.88 456.88 payment made open balance0.000.000.0000150.6042150.6042150.600.000.00306.280.000.00000.00366.06 366.060.00
1252023-03-31original 456.845456.84 confirmed 456.84 456.84 payment made closed balance0.000.000.000090.782990.782990.780.000.00366.060.000.00000.000.000.000.00
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
namespace FSharp.Finance
namespace FSharp.Finance.Personal
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>
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 override ToString: unit -> string static member (-) : d1: Date * d2: Date -> TimeSpan ...
<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>
[<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 PaymentCount: int MaxDuration: Duration }
<summary> a regular schedule based on a unit-period config with a specific number of payments with an auto-calculated amount </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>
union case UnitPeriod.Config.Monthly: MonthMultiple: int * Year: int * Month: int * Day: int -> UnitPeriod.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 Duration = | Unlimited | Maximum of Length: int<DurationDay> * FromDate: Date
<summary> a length of time in whole days measured from a start date </summary>
union case Duration.Unlimited: Duration
<summary> unrestricted length of time </summary>
type PaymentConfig = { ScheduledPaymentOption: ScheduledPaymentOption CloseBalanceOption: CloseBalanceOption PaymentRounding: Rounding MinimumPayment: MinimumPayment PaymentTimeout: int<DurationDay> }
<summary> how to treat scheduled payments </summary>
[<Struct>] type ScheduledPaymentOption = | AsScheduled | AddChargesAndInterest
<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>
[<Struct>] type CloseBalanceOption = | LeaveOpenBalance | IncreaseFinalPayment | AddSingleExtraPayment | AddMultipleExtraPayments
<summary> how to handle a final balance if not closed: leave it open or modify/add payments at the end of the schedule </summary>
union case CloseBalanceOption.LeaveOpenBalance: CloseBalanceOption
<summary> do not modify the final payment and leave any open balance as is </summary>
union case Rounding.RoundUp: Rounding
<summary> round up to the specified precision (= ceiling) </summary>
[<Struct>] type MinimumPayment = | NoMinimumPayment | DeferOrWriteOff of DeferOrWriteOff: int64<Cent> | ApplyMinimumPayment of ApplyMinimumPayment: int64<Cent>
<summary> how to handle cases where the payment due is less than the minimum that payment providers can process </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>
module Fee from FSharp.Finance.Personal
<summary> a product fee &gt; NOTE: differences between fees and charges: &gt; - fees are up-front amounts paid under agreed terms for receiving an advance &gt; - fees are added to the principal balance and therefore accrue interest </summary>
Multiple items
module Config from FSharp.Finance.Personal.Fee
<summary> options specifying the types of fees, their amounts, and any restrictions on these </summary>

--------------------
type Config = { FeeTypes: FeeType array Rounding: Rounding FeeAmortisation: FeeAmortisation SettlementRefund: SettlementRefund }
<summary> options specifying the types of fees, their amounts, and any restrictions on these </summary>
val initialRecommended: Fee.Config
<summary> a default config value, with no fees but recommended settings </summary>
module Charge from FSharp.Finance.Personal
<summary> a penalty charge &gt; NB: differences between charges and fees: &gt; - charges are not up-front amounts, they are incurred as a result of a breach of agreed terms &gt; - charges are not added to the principal balance and do not therefore accrue interest </summary>
union case Charge.ChargeType.LatePayment: LatePayment: Amount -> Charge.ChargeType
<summary> a charge incurred because a scheduled payment was not made on time or in full </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 * Rounding: Rounding | Simple of Simple: int64<Cent>
<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.Simple: Simple: int64<Cent> -> Amount
<summary> a fixed fee </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
<summary> the type of rounding, specifying midpoint-rounding where necessary </summary>
union case Rounding.RoundDown: Rounding
<summary> round down to the specified precision (= floor) </summary>
[<Struct>] type ChargeGrouping = | OneChargeTypePerDay | OneChargeTypePerProduct | AllChargesApplied
<summary> options on how to handle multiple charges </summary>
union case Charge.ChargeGrouping.OneChargeTypePerDay: Charge.ChargeGrouping
<summary> only one charge of any type may be applied per day </summary>
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
<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
<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>

--------------------
type Percent = | Percent of decimal
<summary> a percentage, e.g. 42%, as opposed to its decimal representation 0.42m </summary>
union case ValueOption.ValueSome: 'T -> ValueOption<'T>
union case Amount.Percentage: Percentage: Percent * Restriction: Restriction * Rounding: Rounding -> 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>
<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.NoRounding: Rounding
<summary> do not round at all </summary>
union case Interest.Rate.Zero: Interest.Rate
<summary> a zero rate </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
<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> }
<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 amortisationSchedule: Amortisation.Schedule
module Amortisation from FSharp.Finance.Personal
<summary> calculating the principal balance over time, taking into account the effects of charges, interest and fees </summary>
val generate: sp: Parameters -> settlementDay: SettlementDay voption -> trimEnd: bool -> actualPayments: Map<int<OffsetDay>,ActualPayment array> -> Amortisation.Schedule
<summary> generates an amortisation schedule and final statistics </summary>
union case ValueOption.ValueNone: ValueOption<'T>
val html: string
Amortisation.Schedule.ScheduleItems: Map<int<OffsetDay>,Amortisation.ScheduleItem>
<summary> a list of amortisation items, showing the events and calculations for a particular offset day </summary>
module Formatting from FSharp.Finance.Personal
<summary> convenience module for generating HTML tables, optimised for amortisation schedules </summary>
val generateHtmlFromMap: hideProperties: string array -> data: Map<'a,'b> -> string (requires comparison)
<summary> generates a formatted HTML table from a map with the index name "Day" </summary>

Type something to start searching.