金融领域还款算法

项目地址

https://git.oschina.net/haxzheng/repayment.git


1.等本等息
总收益=借款金额×借款利率×借款月数n/12
总本金=借款金额
总本息=总收益+总本金
2至n月收益=月还利息=总收益/借款月数,保留两位小数,第三位起舍去
2至n月收本金=月还本金=向下取整(借款金额/借款月数)
2至n月结算额=月还款额=月利息+月还本金,保留两位小数,不足补0,下同
1月收益=总收益-求和(2至n月收益)
1月本金=总本金-求和(2至n月本金)
1月本金及收益=1月收益+1月本金

package org.xzheng.repayment.service.impl;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;
import org.xzheng.repayment.util.DateUtil;

/**
 * 等本等息。 误差放在第一个月
 */
public class RePayAlgorithmAverageCapital implements IRepayPlanGenerator {

	@Override
	public Map<Integer, ReplayPlan> getRepayPlan(double principal, double yearRate, int totalmonth, Date beginDate) {
		Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
		DateTime before = null;
		DateTime after = null;

		DateTime beginBase = new DateTime(beginDate);
		beginBase = beginBase.plusDays(-1);

		// 总收益=借款金额×借款利率×借款月数n/12
		BigDecimal totalInterest = new BigDecimal(principal);
		try {
			totalInterest = totalInterest.multiply(new BigDecimal(yearRate)).multiply(new BigDecimal(totalmonth))
					.divide(new BigDecimal(12), 2, BigDecimal.ROUND_HALF_UP);
		} catch (Exception e) {
			e.printStackTrace();
		}
		totalInterest = totalInterest.setScale(2, BigDecimal.ROUND_HALF_UP);

		// 2至n月收益
		BigDecimal perMonthInterest = totalInterest.divide(new BigDecimal(totalmonth), 2, BigDecimal.ROUND_DOWN);

		// 2至n月收本金
		BigDecimal perMonthPrincipal = new BigDecimal(principal);
		perMonthPrincipal = perMonthPrincipal.divide(new BigDecimal(totalmonth), 2, BigDecimal.ROUND_DOWN);

		// 2至n月收益,总额
		BigDecimal totaInterestWithOutFirst = perMonthInterest.multiply(new BigDecimal(totalmonth - 1));
		// 2至n月收本金,总额
		BigDecimal totaPrincipalWithOutFirst = perMonthPrincipal.multiply(new BigDecimal(totalmonth - 1));

		for (int i = 0; i < totalmonth; i++) {
			int period = i + 1;

			if (after == null) {
				before = new DateTime(beginDate);
			} else {
				before = after.plusDays(1);
			}

			after = new DateTime(DateUtil.add_month(beginBase.toDate(), period));

			int days = Days.daysBetween(before, after).getDays() + 1;

			ReplayPlan plan = new ReplayPlan(period, yearRate, perMonthInterest, perMonthPrincipal, before.toDate(),
					after.toDate(), days);

			map.put(period, plan);
		}

		ReplayPlan firstMonth = map.get(1);

		// 1月收益=总收益-求和(2至n月收益)
		BigDecimal firstInterest = new BigDecimal(totalInterest.doubleValue() - totaInterestWithOutFirst.doubleValue());
		firstInterest = firstInterest.setScale(2, BigDecimal.ROUND_HALF_UP);
		firstMonth.setInterest(firstInterest);
		// 1月本金=总本金-求和(2至n月本金)
		BigDecimal firstPrincipal = new BigDecimal(principal - totaPrincipalWithOutFirst.doubleValue());
		firstPrincipal = firstPrincipal.setScale(2, BigDecimal.ROUND_HALF_UP);
		firstMonth.setPrincipal(firstPrincipal);
		// 1月本金及收益=1月收益+1月本金
		firstMonth.setTotalAmount(firstInterest.add(firstPrincipal));

		return map;
	}

	public static void main(String[] args) throws Exception {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date begin = sdf.parse("2017-08-30");
		Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmAverageCapital().getRepayPlan(4000, 0.1238, 24, begin);
		System.out.println("等本等息");
		for (int i = 1; i <= detailPlan.size(); i++) {
			ReplayPlan plan = detailPlan.get(i);
			System.out.println(plan);
		}
	}
}
测试结果:

等本等息
期数:1,年化利率:0.1238,利息:41.42,本金:166.82,总额:208.24,计息天数:31,计息开始日期:2017-08-30,计息结束日:2017-09-29
期数:2,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2017-09-30,计息结束日:2017-10-29
期数:3,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2017-10-30,计息结束日:2017-11-29
期数:4,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2017-11-30,计息结束日:2017-12-29
期数:5,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2017-12-30,计息结束日:2018-01-29
期数:6,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:29,计息开始日期:2018-01-30,计息结束日:2018-02-27
期数:7,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-02-28,计息结束日:2018-03-29
期数:8,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-03-30,计息结束日:2018-04-29
期数:9,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-04-30,计息结束日:2018-05-29
期数:10,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-05-30,计息结束日:2018-06-29
期数:11,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-06-30,计息结束日:2018-07-29
期数:12,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-07-30,计息结束日:2018-08-29
期数:13,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-08-30,计息结束日:2018-09-29
期数:14,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-09-30,计息结束日:2018-10-29
期数:15,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-10-30,计息结束日:2018-11-29
期数:16,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2018-11-30,计息结束日:2018-12-29
期数:17,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2018-12-30,计息结束日:2019-01-29
期数:18,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:29,计息开始日期:2019-01-30,计息结束日:2019-02-27
期数:19,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2019-02-28,计息结束日:2019-03-29
期数:20,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2019-03-30,计息结束日:2019-04-29
期数:21,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2019-04-30,计息结束日:2019-05-29
期数:22,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2019-05-30,计息结束日:2019-06-29
期数:23,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:30,计息开始日期:2019-06-30,计息结束日:2019-07-29
期数:24,年化利率:0.1238,利息:41.26,本金:166.66,总额:207.92,计息天数:31,计息开始日期:2019-07-30,计息结束日:2019-08-29

2.按月付息到期还本

每月支付的利息=本金×年利率÷365×持有天数

package org.xzheng.repayment.service.impl;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;
import org.xzheng.repayment.util.DateUtil;

/**
 * 按月付息,到期还本.
 */
public class RePayAlgorithmMonth implements IRepayPlanGenerator {

	@Override
	public Map<Integer, ReplayPlan> getRepayPlan(double principal, double yearRate, int totalmonth, Date begin) {
		Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
		DateTime before = null;
		DateTime after = null;
		// 需求:当日作为计息开始日,计息结束日期为下个月当天的前一日。
		// 算法:开始日期先减1天,以次为基线,循环在月份上加上期数。月份的加法使用数据库算法。
		// 解释:
		// 1.月份加法数据库算法和java算法差异:9-30加1月,数据库算法结果是10-31,java算法结果是10-30。
		// 对java算法进行改造,加月份前先加1天再加月份最后再减1天

		// 2。示例
		// 计息开始日:8-30、8-31、9-1
		// A.先加月份再减1天: 加1月9-30、9-30、10-1,减1天9-29、9-29、9-30。
		// B.先减1天再加月份: 减1天8-29、8-30、8-31,加1月9-29、9-30、9-30。
		// 计息开始日:9-29、9-30、10-1
		// C.先加月份再减1天: 加1月10-29、10-31、11-1,减1天10-28、10-30、10-31。
		// D.先减1天再加月份: 减1天9-28、9-29、9-30,加1月10-28、10-29、10-31。
		// 其中:
		// 示例A中计息开始日8-31的计息结束日为9-29,少了1天
		// 示例C中计息开始日9-30计息结束日为10-30,多了1天
		// 先减1天再加月份,算法比较合理

		// 3.以开始日减一天作为基线,循环在月份上加上期数,如果有大小月情况出现,误差都是一致的。如果每期都是在上期的结果上减1天再加月份,每期都有可能出现误差。
		DateTime beginBase = new DateTime(begin);
		beginBase = beginBase.plusDays(-1);
		for (int i = 0; i < totalmonth; i++) {
			int period = i + 1;

			if (after == null) {
				before = new DateTime(begin);
			} else {
				before = after.plusDays(1);
			}

			after = new DateTime(DateUtil.add_month(beginBase.toDate(), period));

			int days = Days.daysBetween(before, after).getDays() + 1;
			BigDecimal interest = new BigDecimal(principal)
					.multiply(new BigDecimal(yearRate).multiply(new BigDecimal(days)));
			interest = interest.divide(new BigDecimal(365), 2, BigDecimal.ROUND_HALF_UP);

			BigDecimal bprincipal = new BigDecimal(0);
			if (period == totalmonth) {
				bprincipal = new BigDecimal(principal);
			}

			ReplayPlan plan = new ReplayPlan(period, yearRate, interest, bprincipal, before.toDate(), after.toDate(),
					days);

			map.put(period, plan);
		}
		return map;
	}

	public static void main(String[] args) throws Exception {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date begin = sdf.parse("2017-07-21");
		Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmMonth().getRepayPlan(3000, 0.1238, 3, begin);
		System.out.println("按月付息到期还本");
		for (int i = 1; i <= detailPlan.size(); i++) {
			ReplayPlan plan = detailPlan.get(i);
			System.out.println(plan);
		}
	}
}
测试结果

按月付息到期还本
期数:1,年化利率:0.1238,利息:31.54,本金:0.00,总额:31.54,计息天数:31,计息开始日期:2017-07-21,计息结束日:2017-08-20
期数:2,年化利率:0.1238,利息:31.54,本金:0.00,总额:31.54,计息天数:31,计息开始日期:2017-08-21,计息结束日:2017-09-20
期数:3,年化利率:0.1238,利息:30.53,本金:3000.00,总额:3030.53,计息天数:30,计息开始日期:2017-09-21,计息结束日:2017-10-20

3.一次性还本付息

到期一次性还本付息总额=投资本金×[1+年利率÷365×投资天数]

package org.xzheng.repayment.service.impl;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.joda.time.DateTime;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;

/**
 * 一次性还本付息算法.
 */
public class RePayAlgorithmOnce implements IRepayPlanGenerator {

	@Override
	public Map<Integer, ReplayPlan> getRepayPlan(double invest, double yearRate, int days, Date begin) {
		Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
		BigDecimal interest = new BigDecimal(invest).multiply(new BigDecimal(yearRate)).multiply(new BigDecimal(days))
				.divide(new BigDecimal(365), 2, BigDecimal.ROUND_HALF_UP);
		BigDecimal principal = new BigDecimal(invest);
		DateTime dt = new DateTime(begin);
		DateTime after = dt.plusDays(days - 1);

		ReplayPlan plan = new ReplayPlan(1, yearRate, interest, principal, begin, after.toDate(), days);
		map.put(1, plan);
		return map;
	}

	public static void main(String[] args) throws Exception {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

		DateTime start = new DateTime(sdf.parse("2017-07-02"));

		Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmOnce().getRepayPlan(3000, 0.1, 10, start.toDate());
		System.out.println("一次性还本付息");
		for (int i = 1; i <= detailPlan.size(); i++) {
			ReplayPlan plan = detailPlan.get(i);
			System.out.println(plan);
		}
	}
}
测试结果

一次性还本付息
期数:1,年化利率:0.1,利息:8.22,本金:3000.00,总额:3008.22,计息天数:10,计息开始日期:2017-07-02,计息结束日:2017-07-11

4.等额本息

每月偿还本息=〔贷款本金×月利率×(1+月利率)^还款月数〕÷〔(1+月利率)^还款月数-1〕

每月偿还利息=贷款本金×月利率×〔(1+月利率)^还款月数-(1+月利率)^(还款月序号-1)〕÷〔(1+月利率)^还款月数-1〕

每月偿还本金 = 每月偿还本息 - 每月偿还利息

package org.xzheng.repayment.service.impl;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.xzheng.repayment.entity.ReplayPlan;
import org.xzheng.repayment.service.IRepayPlanGenerator;
import org.xzheng.repayment.util.DateUtil;

/**
 * 等额本息还款,也称定期付息,即借款人每月按相等的金额偿还贷款本息,其中每月贷款利息按月初剩余贷款本金计算并逐月结清。把按揭贷款的本金总额与利息总额相加,
 * 然后平均分摊到还款期限的每个月中。作为还款人,每个月还给银行固定金额,但每月还款额中的本金比重逐月递增、利息比重逐月递减。
 */
public class RePayAlgorithmPrincipal implements IRepayPlanGenerator {

	@Override
	public Map<Integer, ReplayPlan> getRepayPlan(double invest, double yearRate, int totalmonth, Date begin) {
		Map<Integer, ReplayPlan> map = new HashMap<Integer, ReplayPlan>();
		double monthRate = yearRate / 12;
		DateTime before = null;
		DateTime after = null;

		DateTime beginBase = new DateTime(begin);
		beginBase = beginBase.plusDays(-1);

		// 每月偿还本息=〔贷款本金×月利率×(1+月利率)^还款月数〕÷〔(1+月利率)^还款月数-1〕
		BigDecimal monthIncome = new BigDecimal(invest)
				.multiply(new BigDecimal(monthRate * Math.pow(1 + monthRate, totalmonth)))
				.divide(new BigDecimal(Math.pow(1 + monthRate, totalmonth) - 1), 2, BigDecimal.ROUND_DOWN);

		for (int i = 0; i < totalmonth; i++) {
			int period = i + 1;

			if (after == null) {
				before = new DateTime(begin);
			} else {
				before = after.plusDays(1);
			}

			after = new DateTime(DateUtil.add_month(beginBase.toDate(), period));

			int days = Days.daysBetween(before, after).getDays() + 1;

			BigDecimal multiply = new BigDecimal(invest).multiply(new BigDecimal(monthRate));
			BigDecimal sub = new BigDecimal(Math.pow(1 + monthRate, totalmonth))
					.subtract(new BigDecimal(Math.pow(1 + monthRate, i - 1)));

			// 每月偿还利息=贷款本金×月利率×〔(1+月利率)^还款月数-(1+月利率)^(还款月序号-1)〕÷〔(1+月利率)^还款月数-1〕
			BigDecimal monthInterest = multiply.multiply(sub)
					.divide(new BigDecimal(Math.pow(1 + monthRate, totalmonth) - 1), 6, BigDecimal.ROUND_DOWN);
			monthInterest = monthInterest.setScale(2, BigDecimal.ROUND_DOWN);

			// 本金 = 每月偿还本息 - 每月偿还利息
			BigDecimal monthPrincipal = monthIncome.subtract(monthInterest);

			ReplayPlan plan = new ReplayPlan(period, yearRate, monthInterest, monthPrincipal, before.toDate(),
					after.toDate(), days);
			map.put(period, plan);
		}

		return map;
	}

	public static void main(String[] args) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date begin = sdf.parse("2017-07-23");
		Map<Integer, ReplayPlan> detailPlan = new RePayAlgorithmPrincipal().getRepayPlan(3000, 0.1, 3, begin);
		System.out.println("等额本息");
		for (int i = 1; i <= detailPlan.size(); i++) {
			ReplayPlan plan = detailPlan.get(i);
			System.out.println(plan);
		}
	}
}

测试结果

等额本息
期数:1,年化利率:0.1,利息:33.19,本金:983.52,总额:1016.71,计息天数:31,计息开始日期:2017-07-23,计息结束日:2017-08-22
期数:2,年化利率:0.1,利息:24.99,本金:991.72,总额:1016.71,计息天数:31,计息开始日期:2017-08-23,计息结束日:2017-09-22
期数:3,年化利率:0.1,利息:16.73,本金:999.98,总额:1016.71,计息天数:30,计息开始日期:2017-09-23,计息结束日:2017-10-22

PS:日期月份加1算法

package org.xzheng.repayment.util;

import java.util.Calendar;
import java.util.Date;

import org.apache.commons.lang.time.FastDateFormat;

public class DateUtil {

	public static String dateToString(Date date, String format) {
		FastDateFormat fdf = FastDateFormat.getInstance(format);
		if (null == date)
			return null;
		return fdf.format(date);
	}

	/**
	 * 增加月份,解决大小月最后一天问题
	 * 
	 * @param date
	 * @param month
	 * @return
	 */
	public static Date add_month(Date date, int month) {
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		c.add(Calendar.DAY_OF_MONTH, 1);
		c.add(Calendar.MONTH, month);
		c.add(Calendar.DAY_OF_MONTH, -1);
		return c.getTime();
	}

}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值