From 2d8c9679af0b3a0a4f7daba18ab115da0e55c0d1 Mon Sep 17 00:00:00 2001 From: Simon Treanor Date: Thu, 11 Apr 2024 18:55:44 +0100 Subject: [PATCH] promotional interest rates --- src/Amortisation.fs | 26 +++-- src/Calculation.fs | 11 +- src/Currency.fs | 9 +- src/DateDay.fs | 1 - src/FeesAndCharges.fs | 7 +- src/Interest.fs | 121 +++++++++++-------- src/PaymentSchedule.fs | 6 +- src/Percentages.fs | 4 +- src/Quotes.fs | 6 +- src/Rescheduling.fs | 8 +- src/ValueOption.fs | 1 - tests/ActualPaymentTests.fs | 83 ++++++------- tests/ActualPaymentTestsExtra.fs | 70 +++++------ tests/AprUnitedKingdomTests.fs | 1 + tests/AprUsActuarialTests.fs | 1 + tests/AprUsActuarialTestsExtra.fs | 1 + tests/EdgeCaseTests.fs | 50 ++++---- tests/FSharp.Finance.Personal.Tests.fsproj | 1 + tests/InterestTests.fs | 108 +++++++++++++++++ tests/PaymentScheduleTests.fs | 14 ++- tests/QuoteTests.fs | 128 +++++++++++---------- tests/SettlementTests.fs | 14 ++- tests/UnitPeriodConfigTests.fs | 22 ++-- 23 files changed, 435 insertions(+), 258 deletions(-) create mode 100644 tests/InterestTests.fs diff --git a/src/Amortisation.fs b/src/Amortisation.fs index d5441b6..8f0aa9b 100644 --- a/src/Amortisation.fs +++ b/src/Amortisation.fs @@ -141,7 +141,6 @@ module Amortisation = let calculate sp intendedPurpose (appliedPayments: AppliedPayment array) = let asOfDay = (sp.AsOfDate - sp.StartDate).Days * 1 - let dailyInterestRate = sp.Interest.Rate |> Interest.Rate.daily let totalInterestCap = sp.Interest.Cap.Total |> Interest.Cap.total sp.Principal let feesTotal = Fees.total sp.Principal sp.FeesAndCharges.Fees |> Cent.fromDecimalCent (ValueSome sp.Calculation.RoundingOptions.FeesRounding) @@ -158,24 +157,33 @@ module Amortisation = else OpenBalance + let earlySettlementDate = match intendedPurpose with IntendedPurpose.Quote _ -> ValueSome sp.AsOfDate | _ -> ValueNone + + let isWithinGracePeriod d = int d <= int sp.Interest.InitialGracePeriod + + let isSettledWithinGracePeriod = + earlySettlementDate + |> ValueOption.map ((OffsetDay.fromDate sp.StartDate) >> int >> isWithinGracePeriod) + |> ValueOption.defaultValue false + + let dailyInterestRates fromDay toDay = Interest.dailyRates sp.StartDate isSettledWithinGracePeriod sp.Interest.StandardRate sp.Interest.PromotionalRates fromDay toDay + appliedPayments |> Array.scan(fun ((si: ScheduleItem), (a: Accumulator)) ap -> let advances = if ap.AppliedPaymentDay = 0 then [| sp.Principal |] else [||] // note: assumes single advance on day 0L - let earlySettlementDate = match intendedPurpose with IntendedPurpose.Quote _ -> ValueSome sp.AsOfDate | _ -> ValueNone - let interestChargeableDays = Interest.chargeableDays sp.StartDate earlySettlementDate sp.Interest.InitialGracePeriod sp.Interest.Holidays si.OffsetDay ap.AppliedPaymentDay - let newInterest = if si.PrincipalBalance <= 0L then match sp.Calculation.NegativeInterestOption with | ApplyNegativeInterest -> - let dailyInterestRate = sp.Interest.RateOnNegativeBalance |> ValueOption.map Interest.Rate.daily |> ValueOption.defaultValue (Percent 0m) - Interest.calculate 0m (si.PrincipalBalance + si.FeesBalance) dailyInterestRate interestChargeableDays + dailyInterestRates si.OffsetDay ap.AppliedPaymentDay + |> Array.map(fun dr -> { dr with InterestRate = sp.Interest.RateOnNegativeBalance |> ValueOption.defaultValue Interest.Rate.Zero }) + |> Interest.calculate (si.PrincipalBalance + si.FeesBalance) ValueNone (ValueSome sp.Calculation.RoundingOptions.InterestRounding) | DoNotApplyNegativeInterest -> 0m else - let dailyInterestCap = sp.Interest.Cap.Daily |> Interest.Cap.daily (si.PrincipalBalance + si.FeesBalance) interestChargeableDays - Interest.calculate dailyInterestCap (si.PrincipalBalance + si.FeesBalance) dailyInterestRate interestChargeableDays + dailyInterestRates si.OffsetDay ap.AppliedPaymentDay + |> Interest.calculate (si.PrincipalBalance + si.FeesBalance) sp.Interest.Cap.Daily (ValueSome sp.Calculation.RoundingOptions.InterestRounding) let cappedNewInterest = if a.CumulativeInterest + newInterest >= totalInterestCap then totalInterestCap - a.CumulativeInterest else newInterest @@ -503,7 +511,7 @@ module Amortisation = EffectiveInterestRate = if finalPaymentDay = 0 || principalTotal + feesTotal - feesRefund = 0L then 0m else (decimal interestTotal / decimal (principalTotal + feesTotal - feesRefund)) / decimal finalPaymentDay - |> Percent |> Interest.Daily + |> Percent |> Interest.Rate.Daily } /// generates an amortisation schedule and final statistics diff --git a/src/Calculation.fs b/src/Calculation.fs index ba243a3..7e0467f 100644 --- a/src/Calculation.fs +++ b/src/Calculation.fs @@ -76,10 +76,15 @@ module Calculation = let powi (power: int) (base': decimal) = decimal (Math.Pow(double base', double power)) /// raises a decimal to a decimal power - let powm (power: decimal) (base': decimal) = decimal (Math.Pow(double base', double power)) + let powm (power: decimal) (base': decimal) = Math.Pow(double base', double power) /// round a value to n decimal places - let roundTo (places: int) (m: decimal) = Math.Round(m, places) + let roundTo rounding (places: int) (m: decimal) = + match rounding with + | ValueSome (RoundDown) -> 10m |> powi places |> fun f -> if f = 0m then 0m else m * f |> floor |> fun m -> m / f + | ValueSome (RoundUp) -> 10m |> powi places |> fun f -> if f = 0m then 0m else m * f |> ceil |> fun m -> m / f + | ValueSome (Round mpr) -> Math.Round(m, places, mpr) + | ValueNone -> m /// how to round calculated interest and payments [] @@ -103,7 +108,7 @@ module Calculation = /// a holiday, i.e. a period when no interest and/or charges are accrued [] - type Holiday = { + type DateRange = { /// the first date of the holiday period Start: Date /// the last date of the holiday period diff --git a/src/Currency.fs b/src/Currency.fs index 9ae3b28..4fb3256 100644 --- a/src/Currency.fs +++ b/src/Currency.fs @@ -25,7 +25,14 @@ module Currency = m |> Rounding.round rounding |> int64 - |> (( * ) 1L) + |> ( * ) 1L + + /// round a decimal cent value to the specified number of places + let roundTo rounding decimalPlaces (m: decimal) = + m + |> decimal + |> roundTo rounding decimalPlaces + |> ( * ) 1m /// lower to the base currency unit, e.g. $12.34 -> 1234¢ let fromDecimal (m: decimal) = round (ValueSome (Round MidpointRounding.AwayFromZero)) (m * 100m) diff --git a/src/DateDay.fs b/src/DateDay.fs index 398eff6..cb667dd 100644 --- a/src/DateDay.fs +++ b/src/DateDay.fs @@ -3,7 +3,6 @@ namespace FSharp.Finance.Personal open System /// a .NET Framework polyfill equivalent to the DateOnly structure in .NET Core -[] module DateDay = /// 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 diff --git a/src/FeesAndCharges.fs b/src/FeesAndCharges.fs index f08a73c..901c34d 100644 --- a/src/FeesAndCharges.fs +++ b/src/FeesAndCharges.fs @@ -14,6 +14,8 @@ module FeesAndCharges = | FacilitationFee of FacilitationFee:Amount /// a fee charged by a Credit Access Business (CAB) or Credit Services Organisation (CSO) assisting access to third-party financial products | CabOrCsoFee of CabOrCsoFee:Amount + /// a fee charged by a bank or building society for arranging a mortgage + | MortageFee of MortageFee:Amount /// any other type of product fee | CustomFee of FeeType:string * FeeAmount:Amount @@ -27,6 +29,7 @@ module FeesAndCharges = |> Array.sumBy(function | Fee.FacilitationFee amount | Fee.CabOrCsoFee amount + | Fee.MortageFee amount | Fee.CustomFee (_, amount) -> amount |> Amount.total baseAmount ) @@ -76,7 +79,7 @@ module FeesAndCharges = /// determines whether charge are applicable on a given day let areApplicable (startDate: Date) holidays (onDay: int) = holidays - |> Array.collect(fun (ih: Holiday) -> + |> Array.collect(fun (ih: DateRange) -> [| (ih.Start - startDate).Days .. (ih.End - startDate).Days |] ) |> Array.exists(fun d -> d = int onDay) @@ -100,7 +103,7 @@ module FeesAndCharges = /// a list of penalty charges applicable to a product Charges: Charge array /// any period during which charges are not payable - ChargesHolidays: Holiday array + ChargesHolidays: DateRange array /// whether to group charges by type per day ChargesGrouping: ChargesGrouping /// the number of days' grace period after which late-payment charges apply diff --git a/src/Interest.fs b/src/Interest.fs index 7bd1a8a..015cde9 100644 --- a/src/Interest.fs +++ b/src/Interest.fs @@ -11,34 +11,42 @@ module Interest = open DateDay open Percentages - /// calculate the interest accrued on a balance at a particular interest rate over a number of days, optionally capped by a daily amount - let calculate (dailyCap: decimal) (balance: int64) (dailyRate: Percent) (chargeableDays: int) = - decimal balance * Percent.toDecimal dailyRate * decimal chargeableDays - |> min (decimal dailyCap) - |> ( * ) 1m - /// the interest rate expressed as either an annual or a daily rate - [] + [] type Rate = + /// a zero rate + | Zero /// the annual interest rate, or the daily interest rate multiplied by 365 | Annual of Annual:Percent /// the daily interest rate, or the annual interest rate divided by 365 | Daily of Daily:Percent + with + /// used to pretty-print the interest rate for debugging + static member serialise = function + | Zero -> $"ZeroPc" + | Annual (Percent air) -> $"AnnualInterestRate{air}pc" + | Daily (Percent dir) -> $"DailyInterestRate{dir}pc" + + /// calculates the annual interest rate from the daily one + static member annual = function + | Zero -> Percent 0m + | Annual (Percent air) -> air |> Percent + | Daily (Percent dir) -> dir * 365m |> Percent - [] - module Rate = - /// used to pretty-print the interest rate for debugging - let serialise = function - | Annual (Percent air) -> $"AnnualInterestRate{air}pc" - | Daily (Percent dir) -> $"DailyInterestRate{dir}pc" - /// calculates the annual interest rate from the daily one - let annual = function - | Annual (Percent air) -> air |> Percent - | Daily (Percent dir) -> dir * 365m |> Percent - /// calculates the daily interest rate from the annual one - let daily = function - | Annual (Percent air) -> air / 365m |> Percent - | Daily (Percent dir) -> dir |> Percent + /// calculates the daily interest rate from the annual one + static member daily = function + | Zero -> Percent 0m + | Annual (Percent air) -> air / 365m |> Percent + | Daily (Percent dir) -> dir |> Percent + + /// the daily interest rate + [] + type DailyRate = { + /// the day expressed as an offset from the start date + RateDay: int + /// the interest rate applicable on the given day + InterestRate: Rate + } /// the interest cap options [] @@ -66,39 +74,62 @@ module Interest = | ValueSome amount -> Amount.total initialPrincipal amount | ValueNone -> decimal Int64.MaxValue * 1m - /// calculates the daily interest cap - static member daily (balance: int64) (interestChargeableDays: int) = function - | ValueSome amount -> Amount.total balance amount * decimal interestChargeableDays - | ValueNone -> decimal Int64.MaxValue * 1m + /// a promotional interest rate valid during the specified date range + [] + type PromotionalRate = { + DateRange: DateRange + Rate: Rate + } + with + /// creates a map of offset days and promotional interest rates + static member toMap (startDate: Date) promotionalRates = + promotionalRates + |> Array.collect(fun pr -> + [| (pr.DateRange.Start - startDate).Days .. (pr.DateRange.End - startDate).Days |] + |> Array.map(fun d -> d, pr.Rate) + ) + |> Map.ofArray /// interest options [] type Options = { - /// the rate of interest - Rate: Rate + /// the standard rate of interest + StandardRate: Rate /// any total or daily caps on interest Cap: Cap - /// any grace period at the start of a product, if a product is settled before which no interest is payable + /// any grace period at the start of a product, during which if a product is settled no interest is payable InitialGracePeriod: int - /// any date ranges during which no interest is applicable - Holidays: Holiday array + /// any promotional or introductory offers during which a different interest rate is applicable + PromotionalRates: PromotionalRate array /// the interest rate applicable for any period in which a refund is owing RateOnNegativeBalance: Rate voption } - /// calculates the number of interest-chargeable days between two dates - let chargeableDays (startDate: Date) (earlySettlementDate: Date voption) (gracePeriod: int) holidays (fromDay: int) (toDay: int) = - let interestFreeDays = - holidays - |> Array.collect(fun (ih: Holiday) -> - [| (ih.Start - startDate).Days .. (ih.End - startDate).Days |] - ) - |> Array.filter(fun d -> d >= int fromDay && d <= int toDay) - let isWithinGracePeriod d = d <= int gracePeriod - let isSettledWithinGracePeriod = earlySettlementDate |> ValueOption.map(fun sd -> isWithinGracePeriod (sd - startDate).Days) |> ValueOption.defaultValue false - [| int fromDay .. int toDay |] - |> Array.filter(fun d -> not (isSettledWithinGracePeriod && isWithinGracePeriod d)) - |> Array.filter(fun d -> interestFreeDays |> Array.exists ((=) d) |> not) - |> Array.length - |> fun l -> (max 0 (l - 1)) * 1 + /// calculates the interest chargeable on a range of days + let dailyRates (startDate: Date) isSettledWithinGracePeriod standardRate promotionalRates (fromDay: int) (toDay: int) = + let promoRates = promotionalRates |> PromotionalRate.toMap startDate + + [| int fromDay + 1 .. int toDay |] + |> Array.map(fun d -> + let offsetDay = d * 1 + if isSettledWithinGracePeriod then + { RateDay = offsetDay; InterestRate = Rate.Zero } + else + match promoRates |> Map.tryFind d with + | Some rate -> { RateDay = offsetDay; InterestRate = rate } + | None -> { RateDay = offsetDay; InterestRate = standardRate } + ) + + /// calculate the interest accrued on a balance at a particular interest rate over a number of days, optionally capped by a daily amount + let calculate (balance: int64) (dailyInterestCap: Amount voption) interestRounding (dailyRates: DailyRate array) = + let dailyCap = Cap.total balance dailyInterestCap + dailyRates + |> Array.sumBy (fun dr -> + dr.InterestRate + |> Rate.daily + |> Percent.toDecimal + |> fun r -> decimal balance * r * 1m + |> min dailyCap + ) + |> Cent.roundTo interestRounding 8 diff --git a/src/PaymentSchedule.fs b/src/PaymentSchedule.fs index a5e595f..7bbeb05 100644 --- a/src/PaymentSchedule.fs +++ b/src/PaymentSchedule.fs @@ -136,7 +136,6 @@ module PaymentSchedule = let fees = Fees.total sp.Principal sp.FeesAndCharges.Fees |> Cent.fromDecimalCent (ValueSome sp.Calculation.RoundingOptions.FeesRounding) - let dailyInterestRate = sp.Interest.Rate |> Interest.Rate.daily let totalInterestCap = sp.Interest.Cap.Total |> Interest.Cap.total sp.Principal |> Cent.fromDecimalCent (ValueSome sp.Calculation.RoundingOptions.InterestRounding) let roughPayment = if paymentCount = 0 then 0m else (decimal sp.Principal + decimal fees) / decimal paymentCount @@ -151,9 +150,8 @@ module PaymentSchedule = schedule <- paymentDays |> Array.scan(fun si d -> - let interestChargeableDays = Interest.chargeableDays sp.StartDate ValueNone sp.Interest.InitialGracePeriod sp.Interest.Holidays si.Day d - let dailyInterestCap = sp.Interest.Cap.Daily |> Interest.Cap.daily si.Balance interestChargeableDays - let interest = Interest.calculate dailyInterestCap si.Balance dailyInterestRate interestChargeableDays |> decimal |> Cent.round (ValueSome sp.Calculation.RoundingOptions.InterestRounding) + let dailyRates = Interest.dailyRates sp.StartDate false sp.Interest.StandardRate sp.Interest.PromotionalRates si.Day d + let interest = Interest.calculate si.Balance sp.Interest.Cap.Daily (ValueSome sp.Calculation.RoundingOptions.InterestRounding) dailyRates |> decimal |> Cent.round (ValueSome sp.Calculation.RoundingOptions.InterestRounding) let interest' = if si.AggregateInterest + interest >= totalInterestCap then totalInterestCap - si.AggregateInterest else interest let payment' = Cent.round (ValueSome sp.Calculation.RoundingOptions.PaymentRounding) payment let principalPortion = payment' - interest' diff --git a/src/Percentages.fs b/src/Percentages.fs index 5215f72..5c1943c 100644 --- a/src/Percentages.fs +++ b/src/Percentages.fs @@ -1,5 +1,7 @@ namespace FSharp.Finance.Personal +open System + /// a way to unambiguously express percentages and avoid potential confusion with decimal values module Percentages = @@ -14,7 +16,7 @@ module Percentages = /// create a percent value from a decimal let fromDecimal (m: decimal) = m * 100m |> Percent /// round a percent value to two decimal places - let round (places: int) (Percent p) = roundTo places p |> Percent + let round (places: int) (Percent p) = roundTo (MidpointRounding.AwayFromZero |> Round |> ValueSome) places p |> Percent /// convert a percent value to a decimal let toDecimal (Percent p) = p / 100m /// multiply two percentages together diff --git a/src/Quotes.fs b/src/Quotes.fs index 4e5f8c0..a93e49f 100644 --- a/src/Quotes.fs +++ b/src/Quotes.fs @@ -10,7 +10,7 @@ module Quotes = [] type QuoteResult = - | PaymentQuote of PaymentQuote: int64 * OfWhichPrincipal: int64 * OfWhichFees: int64 * OfWhichInterest: int64 * OfWhichCharges: int64 * ProRatedFees: int64 + | PaymentQuote of PaymentQuote: int64 * OfWhichPrincipal: int64 * OfWhichFees: int64 * OfWhichInterest: int64 * OfWhichCharges: int64 * FeesDue: int64 | AwaitPaymentConfirmation | UnableToGenerateQuote @@ -41,7 +41,7 @@ module Quotes = elif pendingPayments <> 0L then AwaitPaymentConfirmation else - let principalPortion, feesPortion, interestPortion, chargesPortion, proRatedFees = + let principalPortion, feesPortion, interestPortion, chargesPortion, feesDue = if si.GeneratedPayment.Value = 0L then 0L, 0L, 0L, 0L, si.FeesDue else @@ -56,7 +56,7 @@ module Quotes = si.GeneratedPayment.Value, 0L, 0L, 0L, si.FeesDue else si.PrincipalPortion, si.FeesPortion, si.InterestPortion, si.ChargesPortion, si.FeesDue - PaymentQuote (si.GeneratedPayment.Value, principalPortion, feesPortion, interestPortion, chargesPortion, proRatedFees) + PaymentQuote (si.GeneratedPayment.Value, principalPortion, feesPortion, interestPortion, chargesPortion, feesDue) return { QuoteType = quoteType QuoteResult = quoteResult diff --git a/src/Rescheduling.fs b/src/Rescheduling.fs index 4051a9b..89b6e61 100644 --- a/src/Rescheduling.fs +++ b/src/Rescheduling.fs @@ -24,10 +24,10 @@ module Rescheduling = PaymentSchedule: CustomerPaymentSchedule /// how to handle interest on negative principal balances NegativeInterestOption: NegativeInterestOption - /// any periods during which interest is not payable - InterestHolidays: Holiday array + /// any promotional or introductory offers during which a different interest rate is applicable + PromotionalInterestRates: Interest.PromotionalRate array /// any period during which charges are not payable - ChargesHolidays: Holiday array + ChargesHolidays: DateRange array /// whether and when to generate a settlement figure FutureSettlementDay: int voption } @@ -66,7 +66,7 @@ module Rescheduling = ScheduleType = PaymentSchedule.ScheduleType.Rescheduled rp.OriginalFinalPaymentDay PaymentSchedule = [| oldPaymentSchedule; newPaymentSchedule |] |> Array.concat |> IrregularSchedule FeesAndCharges = { sp.FeesAndCharges with ChargesHolidays = rp.ChargesHolidays } - Interest = { sp.Interest with InitialGracePeriod = 0; Holidays = rp.InterestHolidays } + Interest = { sp.Interest with InitialGracePeriod = 0; PromotionalRates = rp.PromotionalInterestRates } Calculation = { sp.Calculation with NegativeInterestOption = rp.NegativeInterestOption } } // create the new amortiation schedule diff --git a/src/ValueOption.fs b/src/ValueOption.fs index b11af93..46fd9b2 100644 --- a/src/ValueOption.fs +++ b/src/ValueOption.fs @@ -1,7 +1,6 @@ namespace FSharp.Finance.Personal /// a computation expression enabling easier handling of functions that return value options -[] module ValueOptionCE = type ValueOptionBuilder() = diff --git a/tests/ActualPaymentTests.fs b/tests/ActualPaymentTests.fs index 9d57942..8af7d72 100644 --- a/tests/ActualPaymentTests.fs +++ b/tests/ActualPaymentTests.fs @@ -12,6 +12,7 @@ module ActualPaymentTests = open Calculation open Currency open CustomerPayments + open DateDay open FeesAndCharges open PaymentSchedule open Percentages @@ -73,10 +74,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -120,10 +121,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -167,10 +168,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -214,10 +215,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -285,10 +286,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -356,10 +357,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -432,10 +433,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -506,10 +507,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -581,10 +582,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -621,7 +622,7 @@ module ActualPaymentTests = NetEffect = -280_83L PaymentStatus = Refunded BalanceStatus = ClosedBalance - NewInterest = -18.453041095890410958904107974M + NewInterest = -18.45304110M NewCharges = [||] PrincipalPortion = -280_64L FeesPortion = 0L @@ -657,10 +658,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -732,10 +733,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -807,10 +808,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -884,10 +885,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -933,10 +934,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1005,10 +1006,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1054,10 +1055,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1104,10 +1105,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1155,10 +1156,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1206,10 +1207,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1254,10 +1255,10 @@ module ActualPaymentTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { diff --git a/tests/ActualPaymentTestsExtra.fs b/tests/ActualPaymentTestsExtra.fs index 03600de..d056587 100644 --- a/tests/ActualPaymentTestsExtra.fs +++ b/tests/ActualPaymentTestsExtra.fs @@ -13,12 +13,14 @@ module ActualPaymentTestsExtra = open Calculation open Currency open CustomerPayments + open DateDay open FeesAndCharges open Formatting open PaymentSchedule open Percentages open Rescheduling open Util + open ValueOptionCE let asOfDate = Date(2023, 12, 1) let startDates = [| -90 .. 5 .. 90 |] |> Array.map (asOfDate.AddDays) @@ -45,9 +47,9 @@ module ActualPaymentTestsExtra = let dailyPercentageCap = [| 0.02m .. 0.02m .. 0.2m |] |> Array.map (fun p -> Amount.Percentage(Percent p, ValueNone, ValueNone) |> ValueSome) [| none; dailyFixed; dailyPercentageCap |] |> Array.concat let interestGracePeriods = [| 0 .. 1 .. 7 |] - let interestHolidays = + let promotionalInterestRates = let none = [||] - let some = [| { Holiday.Start = Date(2024, 3, 1); Holiday.End = Date(2024, 12, 31)} |] + let some = [| ({ DateRange = { DateRange.Start = Date(2024, 3, 1); DateRange.End = Date(2024, 12, 31) }; Rate = Interest.Rate.Daily (Percent 0.02m) } : Interest.PromotionalRate) |] [| none; some |] let unitPeriodConfigs (startDate: Date) = let daily = [| 4 .. 32 |] |> Array.map (startDate.AddDays >> UnitPeriod.Config.Daily) @@ -104,10 +106,10 @@ module ActualPaymentTestsExtra = let p = sp.Principal let pf = sp.FeesAndCharges.Fees let pfs = sp.FeesAndCharges.FeesSettlement - let ir = Interest.Rate.serialise sp.Interest.Rate + let ir = Interest.Rate.serialise sp.Interest.StandardRate let ic = sp.Interest.Cap let igp = sp.Interest.InitialGracePeriod - let ih = match sp.Interest.Holidays with [||] -> "()" | ihh -> ihh |> Array.map(fun ih -> $"""({ih.Start.ToString()}-{ih.End.ToString()})""") |> String.concat ";" |> fun s -> $"({s})" + let pir = match sp.Interest.PromotionalRates with [||] -> "()" | pirr -> pirr |> Array.map(fun pr -> $"""({pr.DateRange.Start.ToString()}-{pr.DateRange.End.ToString()}__{Interest.Rate.serialise pr.Rate})""") |> String.concat ";" |> fun s -> $"({s})" let upc, pc = match sp.PaymentSchedule with | RegularSchedule(unitPeriodConfig, paymentCount) -> @@ -116,7 +118,7 @@ module ActualPaymentTestsExtra = let acm = sp.Calculation.AprMethod let pcc = sp.FeesAndCharges.Charges let ro = sp.Calculation.RoundingOptions - let testId = $"""aod{aod}_sd{sd}_p{p}_pf{pf}_pfs{pfs}_ir{ir}_ic{ic}_igp{igp}_ih{ih}_upc{upc}_pc{pc}_acm{acm}_pcc{pcc}_ro{ro}""" + let testId = $"""aod{aod}_sd{sd}_p{p}_pf{pf}_pfs{pfs}_ir{ir}_ic{ic}_igp{igp}_pir{pir}_upc{upc}_pc{pc}_acm{acm}_pcc{pcc}_ro{ro}""" let amortisationSchedule = voption { let! schedule = PaymentSchedule.calculate BelowZero sp @@ -164,7 +166,7 @@ module ActualPaymentTestsExtra = let tic = takeRandomFrom totalInterestCaps let dic = takeRandomFrom dailyInterestCaps let igp = takeRandomFrom interestGracePeriods - let ih = takeRandomFrom interestHolidays + let pir = takeRandomFrom promotionalInterestRates let upc = takeRandomFrom <| unitPeriodConfigs sd let pc = takeRandomFrom paymentCounts let acm = takeRandomFrom aprCalculationMethods @@ -188,10 +190,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = ir + StandardRate = ir Cap = { Total = tic; Daily = dic } InitialGracePeriod = igp - Holidays = ih + PromotionalRates = pir RateOnNegativeBalance = ValueNone } Calculation = { @@ -214,7 +216,7 @@ module ActualPaymentTestsExtra = totalInterestCaps |> Seq.collect(fun tic -> dailyInterestCaps |> Seq.collect(fun dic -> interestGracePeriods |> Seq.collect(fun igp -> - interestHolidays |> Seq.collect(fun ih -> + promotionalInterestRates |> Seq.collect(fun ih -> unitPeriodConfigs sd |> Seq.collect(fun upc -> paymentCounts |> Seq.collect(fun pc -> aprCalculationMethods |> Seq.collect(fun acm -> @@ -239,10 +241,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = ir + StandardRate = ir Cap = { Total = tic; Daily = dic } InitialGracePeriod = igp - Holidays = ih + PromotionalRates = ih RateOnNegativeBalance = ValueNone } Calculation = { @@ -330,10 +332,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -370,7 +372,7 @@ module ActualPaymentTestsExtra = NetEffect = 407_64L PaymentStatus = PaymentMade BalanceStatus = ClosedBalance - NewInterest = 3_30.67257534246575342465756748m + NewInterest = 3_30.67257534m NewCharges = [||] PrincipalPortion = 161_76L FeesPortion = 242_58L @@ -406,10 +408,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -448,7 +450,7 @@ module ActualPaymentTestsExtra = NetEffect = 170_04L PaymentStatus = NotYetDue BalanceStatus = ClosedBalance - NewInterest = 64.650465753424657534246581840m + NewInterest = 64.65046575m NewCharges = [||] PrincipalPortion = 67_79L FeesPortion = 101_61L @@ -484,10 +486,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -510,7 +512,7 @@ module ActualPaymentTestsExtra = FeesSettlement = Fees.SettlementRefund.ProRata PaymentSchedule = RegularFixedSchedule (UnitPeriod.Config.Weekly(2, Date(2022, 9, 1)), 155, 20_00L) NegativeInterestOption = DoNotApplyNegativeInterest - InterestHolidays = [||] + PromotionalInterestRates = [||] ChargesHolidays = [||] FutureSettlementDay = ValueNone } @@ -533,7 +535,7 @@ module ActualPaymentTestsExtra = NetEffect = 9_80L PaymentStatus = NotYetDue BalanceStatus = ClosedBalance - NewInterest = 3.728660273972602739726027772m + NewInterest = 3.72866027m NewCharges = [||] PrincipalPortion = 4_39L FeesPortion = 5_38L @@ -569,10 +571,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.12m) + StandardRate = Interest.Rate.Daily (Percent 0.12m) Cap = { Total = ValueSome <| Amount.Simple 500_00L; Daily = ValueNone } InitialGracePeriod = 7 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -645,10 +647,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -721,10 +723,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 7 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -763,7 +765,7 @@ module ActualPaymentTestsExtra = NetEffect = 142_40L PaymentStatus = NotYetDue BalanceStatus = ClosedBalance - NewInterest = 1_28.41170136986301369863014989m + NewInterest = 1_28.41170136m NewCharges = [||] PrincipalPortion = 134_62L FeesPortion = 6_50L @@ -799,10 +801,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -847,7 +849,7 @@ module ActualPaymentTestsExtra = NetEffect = 18_71L PaymentStatus = NotYetDue BalanceStatus = ClosedBalance - NewInterest = 7.113841095890410958904110304m + NewInterest = 7.11384109m NewCharges = [||] PrincipalPortion = 9_26L FeesPortion = 9_38L @@ -883,10 +885,10 @@ module ActualPaymentTestsExtra = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -931,7 +933,7 @@ module ActualPaymentTestsExtra = NetEffect = 18_71L PaymentStatus = NotYetDue BalanceStatus = ClosedBalance - NewInterest = 7.113841095890410958904110304m + NewInterest = 7.11384109m NewCharges = [||] PrincipalPortion = 18_64L FeesPortion = 0L diff --git a/tests/AprUnitedKingdomTests.fs b/tests/AprUnitedKingdomTests.fs index 290990c..73e07f9 100644 --- a/tests/AprUnitedKingdomTests.fs +++ b/tests/AprUnitedKingdomTests.fs @@ -9,6 +9,7 @@ module AprUnitedKingdomTests = open Apr open Currency + open DateDay open Percentages open Util diff --git a/tests/AprUsActuarialTests.fs b/tests/AprUsActuarialTests.fs index 6964c9a..0c1e052 100644 --- a/tests/AprUsActuarialTests.fs +++ b/tests/AprUsActuarialTests.fs @@ -15,6 +15,7 @@ module AprUsActuarialTests = open Apr open Currency + open DateDay open Percentages open Util open UnitPeriod diff --git a/tests/AprUsActuarialTestsExtra.fs b/tests/AprUsActuarialTestsExtra.fs index d086be3..d51222c 100644 --- a/tests/AprUsActuarialTestsExtra.fs +++ b/tests/AprUsActuarialTestsExtra.fs @@ -12,6 +12,7 @@ module AprActuarialTestsExtra = open ArrayExtension open Apr open Currency + open DateDay open Percentages open Util diff --git a/tests/EdgeCaseTests.fs b/tests/EdgeCaseTests.fs index a91518b..b5fe9f4 100644 --- a/tests/EdgeCaseTests.fs +++ b/tests/EdgeCaseTests.fs @@ -11,11 +11,13 @@ module EdgeCaseTests = open Calculation open Currency open CustomerPayments + open DateDay open FeesAndCharges open PaymentSchedule open Percentages open Quotes open Rescheduling + open ValueOptionCE [] let ``1) Quote returning nothing`` () = @@ -39,10 +41,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -90,10 +92,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -141,10 +143,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -194,10 +196,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -317,10 +319,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -434,10 +436,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -555,10 +557,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -609,10 +611,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -637,7 +639,7 @@ module EdgeCaseTests = { PaymentDay = 58; PaymentDetails = ScheduledPayment (ScheduledPaymentType.Rescheduled 5000L) } |] NegativeInterestOption = DoNotApplyNegativeInterest - InterestHolidays = [||] + PromotionalInterestRates = [||] ChargesHolidays = [||] FutureSettlementDay = ValueSome 88 } @@ -691,10 +693,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -719,7 +721,7 @@ module EdgeCaseTests = { PaymentDay = 58; PaymentDetails = ScheduledPayment (ScheduledPaymentType.Rescheduled 5000L) } |] NegativeInterestOption = DoNotApplyNegativeInterest - InterestHolidays = [||] + PromotionalInterestRates = [||] ChargesHolidays = [||] FutureSettlementDay = ValueSome 88 } @@ -773,10 +775,10 @@ module EdgeCaseTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome (Interest.Rate.Annual (Percent 8m)) } Calculation = { @@ -811,7 +813,7 @@ module EdgeCaseTests = NetEffect = 0L PaymentStatus = NoLongerRequired BalanceStatus = RefundDue - NewInterest = -8.792109589041095890410958135m + NewInterest = -8.79210959m NewCharges = [||] PrincipalPortion = 0L FeesPortion = 0L @@ -820,7 +822,7 @@ module EdgeCaseTests = FeesRefund = 0L PrincipalBalance = -12_94L FeesBalance = 0L - InterestBalance = -21.554849315068493150684929621m + InterestBalance = -21.55484933m ChargesBalance = 0L SettlementFigure = 0L FeesDue = 0L diff --git a/tests/FSharp.Finance.Personal.Tests.fsproj b/tests/FSharp.Finance.Personal.Tests.fsproj index 2452e5a..a60544e 100644 --- a/tests/FSharp.Finance.Personal.Tests.fsproj +++ b/tests/FSharp.Finance.Personal.Tests.fsproj @@ -10,6 +10,7 @@ + diff --git a/tests/InterestTests.fs b/tests/InterestTests.fs new file mode 100644 index 0000000..4109afe --- /dev/null +++ b/tests/InterestTests.fs @@ -0,0 +1,108 @@ +namespace FSharp.Finance.Personal.Tests + +open Xunit +open FsUnit.Xunit + +open FSharp.Finance.Personal + +module InterestTests = + + open Currency + open DateDay + open Interest + open Percentages + + module RateTests = + + [] + let ``Zero rate converted to annual yields 0%`` () = + let actual = Rate.Zero |> Rate.annual + let expected = Percent 0m + actual |> should equal expected + + [] + let ``Zero rate converted to daily yields 0%`` () = + let actual = Rate.Zero |> Rate.daily + let expected = Percent 0m + actual |> should equal expected + + [] + let ``36,5% annual converted to daily yields 0,1%`` () = + let actual = Percent 36.5m |> Rate.Annual |> Rate.daily + let expected = Percent 0.1m + actual |> should equal expected + + [] + let ``10% daily converted to daily yields the same`` () = + let actual = Percent 10m |> Rate.Daily |> Rate.daily + let expected = Percent 10m + actual |> should equal expected + + [] + let ``10% annual converted to annual yields the same`` () = + let actual = Percent 10m |> Rate.Annual |> Rate.annual + let expected = Percent 10m + actual |> should equal expected + + [] + let ``0,1% daily converted to annual yields 36,5%`` () = + let actual = Percent 0.1m |> Rate.Daily |> Rate.annual + let expected = Percent 36.5m + actual |> should equal expected + + module CapTests = + + [] + let ``No cap total on a €100 principal yields a very large number`` () = + let actual = Cap.none.Total |> Cap.total 100_00L + let expected = 92_233_720_368_547_758_07m + actual |> should equal expected + + [] + let ``100% cap total on a €100 principal yields €100`` () = + let actual = Cap.example.Total |> Cap.total 100_00L + let expected = 100_00m + actual |> should equal expected + + module DailyRatesTests = + + [] + let ``Daily rates with no settlement inside the grace period or promotional rates`` () = + let startDate = Date(2024, 4, 10) + let standardRate = Rate.Annual <| Percent 10m + let promotionalRates = [||] + let fromDay = 0 + let toDay = 10 + let actual = dailyRates startDate false standardRate promotionalRates fromDay toDay + let expected = [| 1 .. 10 |] |> Array.map(fun d -> { RateDay = d * 1; InterestRate = Rate.Annual (Percent 10m) }) + actual |> should equal expected + + [] + let ``Daily rates with a settlement inside the grace period, but no promotional rates`` () = + let startDate = Date(2024, 4, 10) + let standardRate = Rate.Annual <| Percent 10m + let promotionalRates = [||] + let fromDay = 0 + let toDay = 10 + let actual = dailyRates startDate true standardRate promotionalRates fromDay toDay + let expected = [| 1 .. 10 |] |> Array.map(fun d -> { RateDay = d * 1; InterestRate = Rate.Zero }) + actual |> should equal expected + + [] + let ``Daily rates with no settlement inside the grace period but with promotional rates`` () = + let startDate = Date(2024, 4, 10) + let standardRate = Rate.Annual <| Percent 10m + let promotionalRates = [| + ({ DateRange = { Start = Date(2024, 4, 10); End = Date(2024, 4, 15) }; Rate = Rate.Annual (Percent 2m) } : Interest.PromotionalRate) + |] + let fromDay = 0 + let toDay = 10 + let actual = dailyRates startDate false standardRate promotionalRates fromDay toDay + let expected = + [| + [| 1 .. 5 |] |> Array.map(fun d -> { RateDay = d * 1; InterestRate = Rate.Annual (Percent 2m) }) + [| 6 .. 10 |] |> Array.map(fun d -> { RateDay = d * 1; InterestRate = Rate.Annual (Percent 10m) }) + |] + |> Array.concat + actual |> should equal expected + diff --git a/tests/PaymentScheduleTests.fs b/tests/PaymentScheduleTests.fs index 0bd3783..873e3fb 100644 --- a/tests/PaymentScheduleTests.fs +++ b/tests/PaymentScheduleTests.fs @@ -12,9 +12,11 @@ module PaymentScheduleTests = open Calculation open Currency open CustomerPayments + open DateDay open FeesAndCharges open PaymentSchedule open Percentages + open ValueOptionCE module Biweekly = let biweeklyParameters principal offset = @@ -37,10 +39,10 @@ module PaymentScheduleTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -128,10 +130,10 @@ module PaymentScheduleTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.798m) + StandardRate = Interest.Rate.Daily (Percent 0.798m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1379,10 +1381,10 @@ module PaymentScheduleTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { diff --git a/tests/QuoteTests.fs b/tests/QuoteTests.fs index 292f09a..9b28899 100644 --- a/tests/QuoteTests.fs +++ b/tests/QuoteTests.fs @@ -12,10 +12,12 @@ module QuoteTests = open Calculation open Currency open CustomerPayments + open DateDay open FeesAndCharges open PaymentSchedule open Percentages open Quotes + open ValueOptionCE [] let ``1) Settlement falling on a scheduled payment date`` () = @@ -39,10 +41,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -81,7 +83,7 @@ module QuoteTests = NetEffect = 1969_72L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = 3_71.12573150684931506849318800m + NewInterest = 3_71.12573150m NewCharges = [||] PrincipalPortion = 1175_80L FeesPortion = 790_21L @@ -121,10 +123,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -163,7 +165,7 @@ module QuoteTests = NetEffect = 2026_50L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = 2_78.34429863013698630136989100m + NewInterest = 2_78.34429863m NewCharges = [||] PrincipalPortion = 1175_80L FeesPortion = 834_21L @@ -203,10 +205,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -245,7 +247,7 @@ module QuoteTests = NetEffect = 2026_50L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = 2_78.34429863013698630136989100m + NewInterest = 2_78.34429863m NewCharges = [||] PrincipalPortion = 1175_80L FeesPortion = 834_21L @@ -285,10 +287,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = { Total = ValueSome <| Amount.Percentage (Percent 100m, ValueNone, ValueSome RoundDown); Daily = ValueNone } InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -363,10 +365,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = { Total = ValueSome <| Amount.Percentage (Percent 100m, ValueNone, ValueSome RoundDown); Daily = ValueNone } InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -441,10 +443,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -483,7 +485,7 @@ module QuoteTests = NetEffect = 3420_03L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = 2_78.34429863013698630136989100m + NewInterest = 2_78.34429863m NewCharges = [||] PrincipalPortion = 1175_80L FeesPortion = 2227_74L @@ -523,10 +525,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -563,7 +565,7 @@ module QuoteTests = NetEffect = 323_10L PaymentStatus = NotYetDue BalanceStatus = OpenBalance - NewInterest = 2_57.39205205479452054794523135m + NewInterest = 2_57.39205205m NewCharges = [||] PrincipalPortion = 110_72L FeesPortion = 209_81L @@ -603,10 +605,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -644,7 +646,7 @@ module QuoteTests = NetEffect = 300_11L PaymentStatus = NotYetDue BalanceStatus = ClosedBalance - NewInterest = 1_14.10005753424657534246576489m + NewInterest = 1_14.10005753m NewCharges = [||] PrincipalPortion = 103_33L FeesPortion = 195_64L @@ -684,10 +686,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -721,7 +723,7 @@ module QuoteTests = NetEffect = 1311_67L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = 16_35.6164383561643835616440000m + NewInterest = 16_35.61643835m NewCharges = [||] PrincipalPortion = 500_00L FeesPortion = 750_00L @@ -761,10 +763,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -806,7 +808,7 @@ module QuoteTests = NetEffect = 1261_73L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = 75_11.9888465753424657534254125m + NewInterest = 75_11.98884657m NewCharges = [||] PrincipalPortion = 471_07L FeesPortion = 706_56L @@ -846,10 +848,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -889,7 +891,7 @@ module QuoteTests = NetEffect = 973_53L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = 1_04.87518082191780821917809273m + NewInterest = 1_04.87518082m NewCharges = [||] PrincipalPortion = 769_46L FeesPortion = 195_68L @@ -929,10 +931,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.798m) + StandardRate = Interest.Rate.Daily (Percent 0.798m) Cap = Interest.Cap.example InitialGracePeriod = 1 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome (Interest.Rate.Annual (Percent 8m)) } Calculation = { @@ -1005,10 +1007,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1086,10 +1088,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1166,10 +1168,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1246,10 +1248,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1326,10 +1328,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1407,10 +1409,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1488,10 +1490,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1531,7 +1533,7 @@ module QuoteTests = NetEffect = -67_95L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = -1.4888767123287671232876711026M + NewInterest = -1.48887672M NewCharges = [||] PrincipalPortion = -67_93L FeesPortion = 0L @@ -1569,10 +1571,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1612,7 +1614,7 @@ module QuoteTests = NetEffect = -72_80L PaymentStatus = Generated Settlement BalanceStatus = ClosedBalance - NewInterest = -4_86.86268493150684931506845055M + NewInterest = -4_86.86268494M NewCharges = [||] PrincipalPortion = -67_93L FeesPortion = 0L @@ -1650,10 +1652,10 @@ module QuoteTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome <| Interest.Rate.Annual (Percent 8m) } Calculation = { @@ -1696,10 +1698,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1745,10 +1747,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1794,10 +1796,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -1843,10 +1845,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome (Interest.Rate.Annual (Percent 8m)) } Calculation = { @@ -1890,10 +1892,10 @@ module QuoteTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueSome (Interest.Rate.Annual (Percent 8m)) } Calculation = { diff --git a/tests/SettlementTests.fs b/tests/SettlementTests.fs index 6a64e60..01b1d0a 100644 --- a/tests/SettlementTests.fs +++ b/tests/SettlementTests.fs @@ -12,10 +12,12 @@ module SettlementTests = open Calculation open Currency open CustomerPayments + open DateDay open FeesAndCharges open PaymentSchedule open Percentages open Quotes + open ValueOptionCE [] let ``1) Final payment due on Friday: what would I pay if I paid it today?`` () = @@ -34,10 +36,10 @@ module SettlementTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -111,10 +113,10 @@ module SettlementTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -188,10 +190,10 @@ module SettlementTests = LatePaymentGracePeriod = 0 } Interest = { - Rate = Interest.Daily (Percent 0.8m) + StandardRate = Interest.Rate.Daily (Percent 0.8m) Cap = Interest.Cap.example InitialGracePeriod = 0 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { diff --git a/tests/UnitPeriodConfigTests.fs b/tests/UnitPeriodConfigTests.fs index 59e4cbb..e250765 100644 --- a/tests/UnitPeriodConfigTests.fs +++ b/tests/UnitPeriodConfigTests.fs @@ -12,10 +12,12 @@ module UnitPeriodConfigTests = open Calculation open Currency open CustomerPayments + open DateDay open FeesAndCharges open PaymentSchedule open Percentages open Quotes + open ValueOptionCE open UnitPeriod module DefaultConfig = @@ -73,10 +75,10 @@ module UnitPeriodConfigTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -150,10 +152,10 @@ module UnitPeriodConfigTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -214,10 +216,10 @@ module UnitPeriodConfigTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -307,10 +309,10 @@ module UnitPeriodConfigTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = { @@ -391,10 +393,10 @@ module UnitPeriodConfigTests = LatePaymentGracePeriod = 3 } Interest = { - Rate = Interest.Rate.Annual (Percent 9.95m) + StandardRate = Interest.Rate.Annual (Percent 9.95m) Cap = Interest.Cap.none InitialGracePeriod = 3 - Holidays = [||] + PromotionalRates = [||] RateOnNegativeBalance = ValueNone } Calculation = {