目录

  • 前提
  • 代码
    • 计算逻辑类
    • 计算实体类
    • 工具类
    • 还款表实体类
    • 测试类
    • 输出结果
    • 通过Excel校验
  • 总结

前提

在我们上个版本迭代中、有个需求是通过给定的收益率计算年利率以及租金、本息拆分的需求、如果是财务人员或者相关的业务同事、
直接拿EXCEL会比较轻松的算出结果、但我们不一样啊、我们是写代码的、要根据他们的需求通过代码的方式实现、首先我用百度查、
百分之九十九不符合我们的业务场景、大多数的博客以及论坛都是直接拿年利率去计算租金、然后通过期利率再进行本息拆分、然后
科学搜索奇奇怪怪的方法很多、但是这其中我还去了github以及stack overflow查找相关的demo或者问答、也都没有相符合的、
最后也只是在Apache官网找到了一个比较官方的通过年利率计算租金的公式、在一筹莫展之际、我们PM给出了思路、目前已知收益
率和还款时间、采用二分法分解年利率、计算出每期租金、然后通过计算出的租金以及还款时间根据XIRR公式计算出的收益率与已知
的收益率相一致、可以认定计算结果是正确的。
最终根据PM的思路以及帮助、完成了代码的实现、财务验收的时候、没有发现问题。以下是实现的代码。

代码

计算逻辑类

package com.songod;import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.formula.functions.FinanceLib;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/***@author Accelerator*@date 2020年7月25日 *@description
*/
@Component
public class Calculate {//求解执行最大尝试次数public static final int maxLimit = 5000;//日志private static final Logger LOG = LoggerFactory.getLogger(Calculate.class);/*** * @author Accelerator* @date 2020年7月25日 下午2:10:34* @param target       目标值* @param max            变量初始化最大值* @param min           变量初始化最小值* @param errorRange    误差* @param obj         测算所有其他数据* @return* @throws ParseException * @description         通过对年利率二分法算出租金、再算出XIRR与目标值做对比*/public BigDecimal getResult(BigDecimal target,BigDecimal max,BigDecimal min,BigDecimal errorRange,CalculateBean obj) throws ParseException {BigDecimal temp = new BigDecimal(0);BigDecimal result = new BigDecimal(0);BigDecimal stepNum = new BigDecimal(2);//遍历for (int i = 1; i <= maxLimit; i++) {temp = max.add(min).divide(stepNum,MathContext.DECIMAL128);result = calculateDetail(temp,obj);//如果(目标值-函数值)的绝对值 - 差误值<=零 则认定计算正确if(result.subtract(target).abs().compareTo(errorRange) <= 0) {LOG.info("单变量求解执行结果小于误差: target:{} - max:{} - min:{} - errorRange:{} - 执行次数:{}",target,max,min,errorRange,i);break;} else if(result.compareTo(target) > 0) {max = temp;} else if(result.compareTo(target) < 0) {min = temp;} else if(result.compareTo(target) == 0) {//如果计算值 = 目标值 则认定计算正确LOG.info("单变量求解执行结果没有误差: target:{} - max:{} - min:{} - errorRange:{} - 执行次数:{}",target,max,min,errorRange,i);break;}//如果上方两个条件均未满足、则以执行最大次数结果为终值if(i == maxLimit) {LOG.info("单变量求解执行结果执行最大次数: target:{} - max:{} - min:{} - errorRange:{} - 执行次数:{}",target,max,min,errorRange,i);}}return temp;}/*** * @author Accelerator* @date 2020年7月25日 下午2:40:03* @param temp      二分年利率值* @param obj     测算对象数据* @return* @throws ParseException * @description */public BigDecimal calculateDetail(BigDecimal temp,CalculateBean calculateBean) throws ParseException {//获取残值String scrapValue = StringUtils.isNotBlank(calculateBean.getScrapValue()) ? calculateBean.getScrapValue() : "0";double scrapValueDou = Double.valueOf(scrapValue);//获取二分年利率double yearRate = temp.doubleValue();calculateBean.setYearRate(yearRate+"");//获取年还款次数int repayRhythm = Integer.valueOf(calculateBean.getRepayRhythm());//计算期利率double perRate = yearRate / repayRhythm;//起租日为当月10号LocalDate date = LocalDate.now();date = LocalDate.of(date.getYear(), date.getMonth(), 10);//获取期数int periods= Integer.parseInt(calculateBean.getPeriods());//获取计算方式(1-先付;2-后付。先付后付的区别在于先付第一期无利息、且第零期与第一期起租日相同)String computeType = calculateBean.getComputeType();//默认每个月还款一次int perMuchMonth = 1;//初始化还款表List<Repayment> list = initRepayment(date, 10, periods, perMuchMonth, computeType);boolean flag = false;//判断计算方式 1-先付;2-后付if(StringUtils.equals("1", computeType)) {flag = true;}//apache 官方PMT计算公式 计算每期租金double perRent = FinanceLib.pmt(yearRate/12,periods,-Double.parseDouble(calculateBean.getFinance()),scrapValueDou,flag);//第零期剩余本金为还款金额(融资额)list.get(0).setRemainingPrinciple(calculateBean.getFinance());//本息拆分for (int i = 1; i < list.size(); i++) {Repayment repayment = list.get(i);//此处省略本息拆分//四舍五入、保留两位小数BigDecimal bg = new BigDecimal(perRent).setScale(2, RoundingMode.UP);repayment.setRent(bg+"");repayment.setCashFlow(bg+"");}//处理第一期现金流list.get(0).setCashFlow("-"+calculateBean.getFinance());//填充还款表集合calculateBean.setList(list);//计算收益率double calXirr= xirrCalculate(calculateBean.getList());return BigDecimal.valueOf(calXirr);}/*** * @author Accelerator* @date 2020年7月25日* @param date* @param monthDay* @param periods* @param perMuchMonth* @param conputeType* @return* @throws ParseException* @description 初始化还款计划表*/public List<Repayment> initRepayment(LocalDate date,int monthDay,int periods,int perMuchMonth,String conputeType) throws ParseException{List<Repayment> list = new ArrayList<Repayment>();//计算还款日期列表List<Date> dateList = repaymentDate(date, monthDay, periods, perMuchMonth, conputeType);if(!CollectionUtils.isEmpty(dateList)) {for (int i = 0; i < dateList.size(); i++) {Repayment repayment = new Repayment().init();//期数repayment.setNumber(String.valueOf(i));//还款日期repayment.setRepaymentDate(formatTime(dateList.get(i)));//集合设值list.add(repayment);}}return list;}/*** * @author Accelerator* @date 2020年7月25日 * @param date             日期* @param monthDay            每月还款日* @param periods          还款期数* @param perMuchMonth      还款频次(默认一月一次)* @param conputeType       计算方式(先付、后付)* @return* @throws ParseException* @description */public List<Date> repaymentDate(LocalDate date,int monthDay,int periods,int perMuchMonth,String conputeType) throws ParseException{LinkedList<Date> linkedList = new LinkedList<Date>();//还款计划表生成在几号、就是最后一期的几号int startDay = date.getDayOfMonth();//还款时间String repaymentDate;//总期数+期初(第零期)int addPeriods = periods +1;for (int i = 0; i < addPeriods; i++) {if(i == 0) {repaymentDate = date.toString();} else {if(!(i == 1 && StringUtils.equals(conputeType, "1"))) {date = date.plusMonths(perMuchMonth);}//如果是最后一期if(i == periods) {//计算最后一期天数int days = date.lengthOfMonth();if(startDay > days) {startDay = days;}date = LocalDate.of(date.getYear(), date.getMonth(), startDay);} else {date = LocalDate.of(date.getYear(), date.getMonth(), monthDay);}repaymentDate = date.toString();}//使用linkedList的原因是因为ArrayList带有自动扩容、当插入对象长度大于当前集合长度时、ArrayList会以1.5倍长度自动扩容linkedList.add(DateUtils.parseDate(repaymentDate));}ArrayList<Date> arrayList = new ArrayList<Date>(linkedList);return arrayList;}/*** * @author Accelerator* @date 2020年7月25日 * @description 处理时间格式* @param date* @return*/public String formatTime(Date date) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");return sdf.format(date);}/*** * @author Accelerator* @date 2020年7月25日 * @param repayments* @description XIRR方法* @return*/public double xirrCalculate(List<Repayment> repayments) {//现金流集合List<Double> cashFlow = new ArrayList<Double>(repayments.size());//日期集合List<Date> date = new ArrayList<Date>(repayments.size());for (Repayment repayment : repayments) {cashFlow.add(Double.valueOf(repayment.getCashFlow()));date.add(DateUtils.parseDate(repayment.getRepaymentDate()));}BigDecimal bdXirr = new BigDecimal(xirr(cashFlow, date, 0.1)).setScale(4, RoundingMode.UP);return Double.valueOf(bdXirr+"");}/*** * @author Accelerator* @date 2020年7月25日 * @param cashFlow            现金流集合* @param date             日期集合* @param guess             猜测值* @return* @description 计算XIRR */public double xirr(List<Double> values,List<Date> dates,double guess) {int  maxXirrLimit = 300;//年天double year = 365.0d;//精度为0.000001double absolute = 1;double minNumber = 0d;double maxNumber = 0d;double result = 0d;double x0 = guess;Date dateNo1 = dates.get(0);int flag = 0;while(flag < maxXirrLimit) {double fv = 0.0d;for (int i = 0; i < dates.size(); i++) {long dayDiff = DateUtils.substractTime(dates.get(i), dateNo1);//求解XIRRfv += values.get(i) / Math.pow(1.0d + x0, dayDiff /year);}if(Math.abs(fv) <= absolute) {result = x0;break;}if(fv > 0) {minNumber = x0;if(maxNumber == 0) {x0 *= 2;} else {x0 = (maxNumber + minNumber) /2;}} else {maxNumber = x0;if(minNumber == 0) {x0 /= 2;} else {x0 = (maxNumber + minNumber) /2;}}flag++;}BigDecimal bd = new BigDecimal(result);bd = bd.setScale(9, RoundingMode.UP);return Double.valueOf(bd+"");}}

计算实体类

package com.songod;import java.util.List;import lombok.Data;/***@author Accelerator*@date 2020年7月25日 *@description
*/@Data
public class CalculateBean {//年还款次数private String repayRhythm;//期数private String periods;//残值private String scrapValue;//计算方式private String computeType;//融资额private String finance;//还款计划表private List<Repayment> list;//计算年利率private String yearRate;}

工具类

package com.songodimport java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;import org.apache.commons.lang3.StringUtils;/***@author Accelerator*@date 2020年7月25日 *@description
*/
public class DateUtils {/*** * @author Accelerator* @date 2020年7月25日 * @description 处理时间格式* @param date* @return*/public static Date parseDate(String date) {Date result = null;try { if(StringUtils.contains(date, "-")) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");result = sdf.parse(date);} else {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");result = sdf.parse(date);}} catch (ParseException e) {e.printStackTrace();}return result;}public static long substractTime(Date endDate,Date startDate) {return  (endDate.getTime() - startDate.getTime()) / (1000L*60*60*24);}}

还款表实体类

package com.songod;import lombok.Data;/***@author Accelerator*@date 2020年7月25日 *@description
*/
@Data
public class Repayment {//租金private String rent;//本金private String principal;//利息private String interest;//剩余本金private String remainingPrinciple;//期数private String number;//还款时间private String repaymentDate;//现金流private String cashFlow;public Repayment initRepayment() {Repayment repayment = new Repayment();repayment.interest = "0";repayment.principal = "0";repayment.remainingPrinciple = "0";repayment.rent = "0";repayment.cashFlow = "0";return repayment;}}

测试类

package com.songod;import java.math.BigDecimal;
import java.text.ParseException;import org.junit.Test;/***@author Accelerator*@date 2020年7月25日 *@description
*/
public class Demo {@Testpublic void getResult() {Calculate calculate = new Calculate();CalculateBean calculateBean = new CalculateBean();calculateBean.setFinance("350000");calculateBean.setPeriods("12");calculateBean.setScrapValue("0");calculateBean.setComputeType("2");calculateBean.setRepayRhythm("12");BigDecimal result = new BigDecimal(0);try {result = calculate.getResult(BigDecimal.valueOf(0.0877), BigDecimal.valueOf(1), BigDecimal.valueOf(0), BigDecimal.valueOf(0.00001), calculateBean);} catch (ParseException e) {e.printStackTrace();}//计算收益率double calXirr= calculate.xirrCalculate(calculateBean.getList());System.out.println(calXirr);System.out.println(calculateBean.getList());}}

输出结果

20:35:57.933 [main] INFO com.songod.Calculate - 单变量求解执行结果小于误差: target:0.0877 - max:0.084716796875 - min:0.08447265625 - errorRange:0.000010 - 执行次数:13
0.0877
[
Repayment(rent=0, principal=0, interest=0, remainingPrinciple=350000, number=0, repaymentDate=2020-07-10, cashFlow=-350000),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=1, repaymentDate=2020-08-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=2, repaymentDate=2020-09-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=3, repaymentDate=2020-10-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=4, repaymentDate=2020-11-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=5, repaymentDate=2020-12-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=6, repaymentDate=2021-01-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=7, repaymentDate=2021-02-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=8, repaymentDate=2021-03-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=9, repaymentDate=2021-04-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=10, repaymentDate=2021-05-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=11, repaymentDate=2021-06-10, cashFlow=30520.36),
Repayment(rent=30520.36, principal=0, interest=0, remainingPrinciple=0, number=12, repaymentDate=2021-07-10, cashFlow=30520.36)
]

通过Excel校验

1.取出每一期的现金流-cashflow
2.取出每一期的起租时间-repaymentDate

总结

以上就是根据收益率计算年利率以及租金等全部方法、其中省略了本息拆分的代码、具体来说本息拆分、
是先算利息(根据当期剩余本金*期利率)、再算本金(当期租金-当期利息)根据各公司的业务不同、实现
的代码具体实现场景也不同。

收益率计算年利率以及每期租金相关推荐

  1. 【R】【课程笔记】04+05 数据预处理+收益率计算

    本文是课程<数据科学与金融计算>第4-5章的学习笔记,主要介绍金融数据处理.收益率计算和R与C++调用,用于知识点总结和代码练习,Q&A为问题及解决方案. 往期回顾: 博文 内容 ...

  2. Python股票数据分析——策略、收益率计算

    技术分析指标 移动平均值.波动率.交易量 基于历史价格信息的技术分析是金融专业人士和感兴趣的业余人士感兴趣的典型任务.在维基百科上可以找到如下定义: 在金融学中,技术分析是通过对过去市场数据(主要是价 ...

  3. 11.1-股票基金历年收益率计算

    文章目录 1. 计算目标 2. 关键问题 3. 获取交易日历 4. 逻辑编写 1. 计算目标 我们想知道,一只股票标的,在之前的几年中,每一年的年化收益率是多少? 如果将每年的年化收益率进行求和汇总, ...

  4. Python笔记-上证指数收益率计算

    代码如下: import pandas as pd from scipy import statsvalueList = [-0.010185, 0.011844, -0.00852, -0.0188 ...

  5. 基金收益率计算1:资管业务、资管产品和基金

    ​资管管理业务,简称"资管业务" 根据<关于规范金融机构资产管理业务的指导意见>(简称"资管新规"): 资产管理业务是指银行.信托.证券.基金.期货 ...

  6. python求年利率_python-3.x - 如何在python 3中计算年利率 - SO中文参考 - www.soinside.com...

    这是代码:P = int(input("Enter starting principle please.\n")) n = int(input("Enter Compou ...

  7. javascript用while循环计算年利率5%,从1000元到5000元需要几年

    var year = 0 var money = 1000 while (money < 5000) { money = money * 0.05 + money year++ } alert( ...

  8. 上市公司综合排名及投资组合构建、收益率计算在线实验闯关

    第1关:基于总体规模与投资效率指标的上市公司综合评价 任务描述 本关任务:根据以下提供的反映上市公司总体规模与投资效率方面指标数据,按年度对上市公司进行综合评价,输出排名前20的上市公司股票简称. 相 ...

  9. 融资租赁租金表、收益指标、财务分摊等相关计算原理

    本文主要介绍融资租赁业务中相关计算的原理主要包括:租金表计算.租金表变更计算.irr.xirr收益率计算.财务分摊计算等.通过案例讲解每一种算法的原理以及用excel是怎么计算出来的. 如需要看视频用 ...

最新文章

  1. python注册登录系统_Python实现简单用户注册信息管理系统
  2. @async注解_史上最全的java spring注解
  3. 28.Node.js 函数和匿名函数
  4. apache目录的访问控制
  5. react打包后图片丢失_使用 webpack 搭建 React 项目
  6. 不买iPhone11的四大理由,最后一个扎心了
  7. dd var tmp .oracle,Oracle 11gR2 RAC ohasd failed to start 解决方法
  8. 杭电1166敌兵布阵(线段树)
  9. 异步tcp通信——APM.Core 服务端概述
  10. 《Javascript高级程序设计》读书笔记(1-3章)
  11. phpmyadmin/scripts/setup.php,Linux下phpMyAdmin安装过程中的问题解决
  12. R语言中与矩阵相关的所有操作(上)
  13. PHP基础知识点汇总(三)
  14. PKI密码学学习笔记
  15. Android使用MediaRecorder的stop方法报stop failed错误的解决方案
  16. 语音质量评价和可懂度评价
  17. 用C#编写一个图片浏览器,实现鼠标控制图片的平移缩放与图片的灰度化
  18. python最简单的爬取邮箱地址_python3爬取网页中的邮箱地址
  19. js文件中引入js的方法
  20. 03-QNX Shell常用指令

热门文章

  1. IDEA部署项目到tomcat运行成功但是页面404的两种原因
  2. pysot-toolkit--eval.py笔记(读取算法结果,根据评价指标计算结果并可视化)
  3. 电脑键盘部分按键失灵_电脑键盘失灵怎么办 三步教你解决问题【图文】
  4. Substrate Tutorials:Start a Private Network (multi-node)
  5. 如何对word中不同页面设置不同页眉页脚
  6. 为什么要阅读《首先,打破一切常规》
  7. elementUI实现table表头展示上、下角标
  8. 4.STM32F407之HAL库——按键
  9. 【解决问题】在jupyter notebook上用 pandas_datareader 获取 yahoo 数据
  10. unraid安装黑群晖虚拟机开机显示 Starting Kernel with USB boot