public static InstallmentPlanRow FromInstallmentPlanCS(InstallmentPlanRowCS installmentPlanRowCS) { if (!Enum.TryParse(installmentPlanRowCS.ActivityKind, true, out ActivityKind activityKind)) { activityKind = ActivityKind.Repayment; } var planRow = new InstallmentPlanRow { ActivityKind = activityKind, Annuity = (decimal)(installmentPlanRowCS.Annuity ?? 0), CashCollateral = (decimal)(installmentPlanRowCS.CashCollateral ?? 0), Date = installmentPlanRowCS.Date ?? DateTime.Today, DeferredPayment = (decimal)(installmentPlanRowCS.DeferredPayment ?? 0), DeferredPaymentInstallment = (decimal)(installmentPlanRowCS.DeferredPaymentInstallment ?? 0), Description = installmentPlanRowCS.Description, Disbursement = (decimal)(installmentPlanRowCS.Disbursement ?? 0), DiscountedCashCollateralFlow = (decimal)(installmentPlanRowCS.DiscountedCashCollateralFlow ?? 0), DiscountedDisbursement = (decimal)(installmentPlanRowCS.DiscountedDisbursement ?? 0), DiscountedNetCashFlow = (decimal)(installmentPlanRowCS.DiscountedNetCashFlow ?? 0), Fee = (decimal)(installmentPlanRowCS.Fee ?? 0), InterestRepayment = (decimal)(installmentPlanRowCS.InterestRepayment ?? 0), NetCashFlow = (decimal)(installmentPlanRowCS.NetCashFlow ?? 0), Ordinal = installmentPlanRowCS.Ordinal ?? 0, OtherExpenses = (decimal)(installmentPlanRowCS.OtherExpenses ?? 0), OutstandingBalance = (decimal)(installmentPlanRowCS.OutstandingBalance ?? 0), PrincipalRepayment = (decimal)(installmentPlanRowCS.PrincipalRepayment ?? 0), StartingBalance = (decimal)(installmentPlanRowCS.StartingBalance ?? 0), YearFrac = 0 }; return(planRow); }
public override void CalculateOffer(PriceCalculationParameters calculationParameters, OfferPriceCalculation priceCalculator, string feeCurrencyConversionMethod) { var ignore = priceCalculator.CalculatePrice(this, calculationParameters).Result; // isCompound i calendarBasis izvuci tokom CalculatePrice da dodatno opisu Napr bool isCompound = false; CalendarBasisKind calendarBasis = CalendarBasisKind.CalendarActActISDA; double yearlyRate = Convert.ToDouble(Napr) / 100; var ir = Conditions.InterestRates.FirstOrDefault(it => it.Kind == InterestRateKinds.RegularInterest); if (ir != null) { isCompound = ir.IsCompound; calendarBasis = ir.CalendarBasis; yearlyRate = Convert.ToDouble(ir.CalculatedRate); Napr = Convert.ToDecimal(yearlyRate); } var startDate = calculationParameters.RequestDate ?? DateTime.Today; List <InstallmentPlanRow> rows = new List <InstallmentPlanRow>(); List <FeeCondition> rowFeeList = new List <FeeCondition>(); List <FeeCondition> fees = Conditions.Fees; fees = FeeCalculation.PrepareFees(fees, Currency, feeCurrencyConversionMethod); decimal runningAmount = Math.Round(Amount, 2); DateTime runningDate = startDate; if (CalculationDate > DateTime.Today) { CalculationDate = runningDate; } else { runningDate = CalculationDate ?? DateTime.Today; } // SLOBA: Fee scheduling, and scheduling in general has to be improved in advanced calculator. decimal totalFeeAmount = 0; if (fees != null) { List <FeeCondition> feeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.EventTriggered && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); totalFeeAmount = Math.Round(FeeCalculation.CalculateFee(Amount, feeList), 4); } NumberOfInstallments = 1; /* Disbursment */ InstallmentPlanRow rowDisbursement = new InstallmentPlanRow(); rowDisbursement.Date = runningDate; rowDisbursement.Ordinal = 0; rowDisbursement.ActivityKind = ActivityKind.Disbursement; rowDisbursement.Description = "Loan disbursement"; rowDisbursement.Disbursement = Amount; rowDisbursement.StartingBalance = 0; rowDisbursement.OutstandingBalance = Amount; rowDisbursement.PrincipalRepayment = 0; rowDisbursement.InterestRepayment = 0; rowDisbursement.NetCashFlow = Amount - totalFeeAmount; rowDisbursement.Fee = totalFeeAmount; rowDisbursement.YearFrac = 0; rows.Add(rowDisbursement); int i = -1; InstallmentPlanRow rowInterest; var intrStartDate = startDate; runningDate = runningDate.AddMonths(1); while (runningDate <= MaturityDate.Value) { i++; var interest = Math.Round(InterestCalculation.CalculateInterest(intrStartDate, runningDate, runningDate, Convert.ToDouble(yearlyRate), 'Y', isCompound, calendarBasis, Convert.ToDouble(runningAmount)) .Sum(it => it.Interest), 2); rowInterest = new InstallmentPlanRow(); rowInterest.Ordinal = i + 1; rowInterest.Date = runningDate; rowInterest.ActivityKind = ActivityKind.Repayment; rowInterest.Description = "Interest and fee repayment"; rowInterest.InterestRepayment = Convert.ToDecimal(interest); rowInterest.PrincipalRepayment = 0; rowInterest.Annuity = rowInterest.InterestRepayment; rowInterest.StartingBalance = Math.Round(runningAmount, 2); rowInterest.OutstandingBalance = Math.Round(runningAmount, 2); #region Fee calculation if (fees != null) { decimal totalRowFee = 0; rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Monthly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } if ((i + 1) % 4 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Quarterly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } if ((i + 1) % 6 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Semiyearly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } if ((i + 1) % 12 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Yearly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } rowInterest.Fee = Math.Round(totalRowFee, 4); } #endregion rowInterest.NetCashFlow = rowInterest.Disbursement - rowInterest.PrincipalRepayment - rowInterest.InterestRepayment - rowInterest.Fee - rowInterest.OtherExpenses; rowInterest.YearFrac = Convert.ToDecimal(InterestCalculation.YearFrac(startDate, runningDate)); //, calendarBasis); rows.Add(rowInterest); intrStartDate = runningDate; runningDate = runningDate.AddMonths(1); } i++; rowInterest = new InstallmentPlanRow(); rowInterest.Ordinal = i + 1; rowInterest.Date = runningDate; rowInterest.ActivityKind = ActivityKind.Repayment; rowInterest.Description = "Principal repayment"; rowInterest.InterestRepayment = 0; rowInterest.PrincipalRepayment = Amount; rowInterest.Annuity = Amount; rowInterest.StartingBalance = Amount; rowInterest.OutstandingBalance = 0; #region Fee calculation rowInterest.Fee = 0; #endregion rowInterest.NetCashFlow = rowInterest.Disbursement - rowInterest.PrincipalRepayment - rowInterest.InterestRepayment - rowInterest.Fee - rowInterest.OtherExpenses; rowInterest.YearFrac = Convert.ToDecimal(InterestCalculation.YearFrac(startDate, runningDate)); //, calendarBasis); rows.Add(rowInterest); NumberOfInstallments = 1; // arrangementRequest.InstallmentPlan = rows; CalculateAPR(rows); }
// SLOBA: term and fees parameters are inconsistently extracted from arrangementRequest public void CalculateInstallmentPlan(string feeCurrencyConversionMethod) { int term = Utility.GetMonthsFromPeriod(Term); List <InstallmentPlanRow> rows = new List <InstallmentPlanRow>(); List <FeeCondition> rowFeeList = new List <FeeCondition>(); List <FeeCondition> fees = Conditions.Fees; fees = FeeCalculation.PrepareFees(fees, Currency, feeCurrencyConversionMethod); decimal runningAmount = Math.Round(Amount, 2); DateTime runningDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day, 0, 0, 0, 0); if (CalculationDate > DateTime.Today) { CalculationDate = runningDate; } else { runningDate = CalculationDate ?? DateTime.Today; } DateTime startDate = runningDate; // SLOBA: Fee scheduling, and scheduling in general has to be improved in advanced calculator. decimal totalFeeAmount = 0; if (fees != null) { List <FeeCondition> feeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.EventTriggered && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); totalFeeAmount = Math.Round(FeeCalculation.CalculateFee(Amount, feeList), 4); } NumberOfInstallments = 1; bool isCompound = false; CalendarBasisKind calendarBasis = CalendarBasisKind.CalendarActActISDA; double yearlyRate = 0; var ir = Conditions.InterestRates.FirstOrDefault(it => it.Kind == InterestRateKinds.RegularInterest); if (ir != null) { isCompound = ir.IsCompound; calendarBasis = ir.CalendarBasis; yearlyRate = Convert.ToDouble(ir.CalculatedRate / 100); } // this is huge simplification. not good double monthlyRate = yearlyRate / 12; if (isCompound) { monthlyRate = Math.Pow(1 + yearlyRate, 1.0000000 / 12.0000000) - 1; } // SLOBA: Logic for calculating cash flow is done in manner that is later hard to extend. Also columns are hardcoded. // SLOBA: This is totally ok for basic calculator though /* Disbursment */ InstallmentPlanRow rowDisbursement = new InstallmentPlanRow(); rowDisbursement.Date = runningDate; rowDisbursement.Ordinal = 0; rowDisbursement.ActivityKind = ActivityKind.Disbursement; rowDisbursement.Description = "Loan disbursement"; rowDisbursement.Disbursement = Amount; rowDisbursement.StartingBalance = 0; rowDisbursement.OutstandingBalance = Amount; rowDisbursement.PrincipalRepayment = 0; rowDisbursement.InterestRepayment = 0; rowDisbursement.NetCashFlow = Amount - totalFeeAmount; rowDisbursement.Fee = totalFeeAmount; rowDisbursement.YearFrac = 0; rows.Add(rowDisbursement); for (var i = 0; i < term; i++) { decimal interest = Math.Round(runningAmount * Convert.ToDecimal(monthlyRate), 2); decimal principal = Math.Round(Annuity - interest, 2); if (i == term - 1) { // last ... corection for decimal places principal = Math.Round(runningAmount, 2); interest = Math.Round(Annuity - principal, 2); } InstallmentPlanRow rowInterest = new InstallmentPlanRow(); rowInterest.Ordinal = i + 1; runningDate = runningDate.AddMonths(1); rowInterest.Date = runningDate; rowInterest.ActivityKind = ActivityKind.Repayment; rowInterest.Description = "Anuity repayment"; rowInterest.InterestRepayment = interest; rowInterest.PrincipalRepayment = principal; rowInterest.Annuity = Annuity; rowInterest.StartingBalance = Math.Round(runningAmount, 2); runningAmount = (runningAmount - principal); rowInterest.OutstandingBalance = Math.Round(runningAmount, 2); #region Fee calculation if (fees != null) { decimal totalRowFee = 0; rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Monthly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } if ((i + 1) % 4 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Quarterly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } if ((i + 1) % 6 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Semiyearly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } if ((i + 1) % 12 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Yearly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } rowInterest.Fee = Math.Round(totalRowFee, 4); } #endregion rowInterest.NetCashFlow = rowInterest.Disbursement - rowInterest.PrincipalRepayment - rowInterest.InterestRepayment - rowInterest.Fee - rowInterest.OtherExpenses; rowInterest.YearFrac = Yearfrac(startDate, runningDate, calendarBasis); rows.Add(rowInterest); NumberOfInstallments += 1; } // arrangementRequest.InstallmentPlan = rows; CalculateAPR(rows); }