/*
* Copyright (c) 2012, 2013, Credit Suisse (Anatole Tresch), Werner Keil. Licensed under the Apache
* License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.javamoney.calc.banking;
import org.javamoney.calc.ComplexCalculation;
import org.javamoney.calc.ComplexType;
import org.javamoney.calc.ComplexValue;
import org.javamoney.calc.common.Rate;
import javax.money.MonetaryAmount;
import javax.money.MonetaryException;
import javax.money.MonetaryOperator;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Objects;
/**
* <img src= "http://www.financeformulas.net/formulaimages/Balloon%20Loan%20Payment%201.gif" />
* <p>
* The balloon loan payment formula is used to calculate the payments on a loan that has a balance
* remaining after all periodic payments are made. Examples of loans that may use the balloon loan
* payment formula would be auto leases, balloon mortgages, and any other form of loan not paid
* in full at its end date.
*
* The formula for a balloon loan payment could also be used for any form of annuity where a
* balance is left after all periodic cash flows are made. An annuity is simply a series of
* periodic payments. An example of how this formula could be applied in a non-loan related
* way would be if an individual has $11,000 sitting in their interest account that must last
* them 2 years, and they need to have a balance of $5,000 at the end of the 2nd year. The
* monthly amount withdrawn could be calculated using the balloon loan payment formula.
*
* One may be enticed to calculate the example above by simply subtracting $5,000 from $11,000
* and calculating the payment based on an ordinary annuity of $6,000. However, one must
* consider that the $5,000 will earn interest over the 2 years leaving a balance higher
* than $5,000 after the 2nd year. This may be acceptable for a smaller amount or for quick
* calculations in ordinary life, however it is not the exact way to calculate the periodic
* payment.
*
* @author Anatole Tresch
* @author Werner Keil
* @see http://www.financeformulas.net/Compound_Interest.html
*/
public final class BalloonLoanPayment implements MonetaryOperator{
private static final ComplexType INPUT_TYPE = new ComplexType.Builder("BalloonLoanPayment:IN")
.addRequiredParameter("balancePV", MonetaryAmount.class)
.addRequiredParameter("balloonAmount", MonetaryAmount.class)
.addRequiredParameter("rate", Rate.class)
.addRequiredParameter("periods", Number.class).build();
public static final ComplexCalculation<ComplexType, MonetaryAmount> CALCULATION = new ComplexCalculation<ComplexType, MonetaryAmount>() {
@Override
public ComplexType getInputType() {
return INPUT_TYPE;
}
@Override
public Class<MonetaryAmount> getResultType() {
return MonetaryAmount.class;
}
@Override
public MonetaryAmount calculate(ComplexValue<ComplexType> input) {
return BalloonLoanPayment.calculate(input.get("balancePV", MonetaryAmount.class),
input.get("balloonAmount", MonetaryAmount.class),
input.get("rate", Rate.class),
input.get("periods", Number.class).intValue()
);
}
};
/**
* the target rate, not null.
*/
private final Rate rate;
/**@Override
public ComplexType getInputType() {
return INPUT_TYPE;
}
@Override
public Class<MonetaryAmount> getResultType() {
return MonetaryAmount.class;
}
@Override
public MonetaryAmount calculate(ComplexValue<ComplexType> input) {
return ;
}
* the periods, >= 0.
*/
private final int periods;
/** The balloon amount. */
private MonetaryAmount balloonAmount;
/**
* Private constructor.
*
* @param rate the target rate, not null.
* @param periods the periods, >= 0.
*/
private BalloonLoanPayment(Rate rate, int periods, MonetaryAmount balloonAmount) {
if (periods <= 0) {
throw new MonetaryException("Periods for BalloonLoanPayment calculation <= 0");
}
this.rate = Objects.requireNonNull(rate);
this.periods = periods;
this.balloonAmount = Objects.requireNonNull(balloonAmount);
}
public int getPeriods() {
return periods;
}
public Rate getRate() {
return rate;
}
public MonetaryAmount getBalloonAmount(){
return balloonAmount;
}
/**
* Access a MonetaryOperator for calculation.
*
* @param rate the target rate, not null.
* @param periods the periods, >= 0.
* @return the operator, never null.
*/
public static BalloonLoanPayment of(Rate rate, int periods, MonetaryAmount balloonAmount){
return new BalloonLoanPayment(rate, periods, balloonAmount);
}
@Override
public MonetaryAmount apply(MonetaryAmount amountPV) {
if(!balloonAmount.getCurrency().equals(amountPV.getCurrency())){
throw new MonetaryException("Currency mismatch: " + balloonAmount.getCurrency() +
" <> "+amountPV.getCurrency());
}
return calculate(amountPV, balloonAmount, rate, periods);
}
@Override
public String toString() {
return "BalloonLoanPayment{" +
"rate=" + rate +
", periods=" + periods +
", balloonAmount=" + balloonAmount +
'}';
}
/**
* Performs the calculation.
*
* @param amountPV the present value, not null.
* @param balloonAmount the balloon amount, not null and currency compatible with {@code amountPV}.
* @param rate the target rate, not null.
* @param periods the periods, >= 0.
* @return the resulting amount, never null.
*/
public static MonetaryAmount calculate(MonetaryAmount amountPV, MonetaryAmount balloonAmount,
Rate rate, int periods) {
if (periods < 0) {
throw new IllegalArgumentException("Periods < 0");
}
final BigDecimal ONE = new BigDecimal(1, MathContext.DECIMAL64);
BigDecimal factor2 = rate.get().divide(
ONE.subtract(
ONE.add(rate.get()).pow(-periods, MathContext.DECIMAL64)), MathContext.DECIMAL64);
MonetaryAmount factor1 = amountPV.subtract(
balloonAmount.getFactory().setNumber(
balloonAmount.getNumber().numberValue(BigDecimal.class).divide(
ONE.add(rate.get()).pow(periods, MathContext.DECIMAL64), MathContext.DECIMAL64)).create());
return factor1.multiply(factor2);
}
}