Расчет APR с использованием Reg Z Приложение J
ХОРОШО. Я новичок в этом сайте, так что "Привет всем"! Ну, я боролся с трудной проблемой на прошлой неделе, и я был бы признателен за любую помощь, которую вы можете мне дать.
Я знаю, что существует множество формул для расчета APR, но я протестировал много формул, и они не обрабатывают нечетные дни должным образом для закрытых (потребительские кредиты). Правительство попыталось оказать нам помощь простым смертным, опубликовав Приложение J к их закону о предоставлении правды. Его можно найти здесь: https://www.fdic.gov/regulations/laws/rules/6500-3550.html
Если вы храбры (!!), вы можете увидеть формулы, которые они предоставляют, которые решат для АТР, включая нечетные дни кредита. Нечетные дни - это дни в начале кредита, которые на самом деле не покрываются регулярным платежом, но проценты по-прежнему начисляются. Например, вы берете кредит на 1000,00 долларов США 20 января 2012 года, а ваш первый платеж - 3 марта 2012 года. У вас есть 10 с лишним дней с 20.01.2012 по 30.01.2012. Все месяцы составляют 30 дней для их расчетов.
Я надеюсь, что кто-то, имеющий значительный опыт работы в Calculus, сможет интерпретировать формулы, которые вы найдете примерно на половине пути в Приложении J. И интерпретировать актуарный метод, который они используют для решения этих формул. Я понимаю итеративный процесс. Сначала я попытался решить эту проблему, используя метод Ньютона-Рафсона, но моя формула для APR не учитывала нечетные дни. Это прекрасно работало в маловероятном тривиальном случае, когда не было странных дней, но боролись с нечетными днями.
Я знаю, что читать этот документ очень сложно! Я добился определенных успехов, но есть определенные вещи, которые я просто не могу понять, как у них дела. Кажется, они вводят несколько вещей, как по волшебству.
В любом случае, спасибо заранее за помощь!:)
3 ответа
Хорошо, вы не шутите, что документ немного сложен для чтения. Решение на самом деле не так уж и плохо, в зависимости от реализации. Я неоднократно терпел неудачу, пытаясь использовать их различные упрощенные форумы, и в конце концов получил это, используя общую формулу вверх (8). Технически это упрощение. Фактическая общая формула будет принимать массивы длины period
для других аргументов и использовать их индексы в цикле. Вы используете этот метод, чтобы получить A и A для шага итерации. Нечетные дни обрабатываются (1.0 + fractions*rate)
, который выглядит как 1 + f i
в документе. Оценить это ставка за период, а не в целом за апрель.
public double generalEquation(int period, double payment, double initialPeriods, double fractions, double rate)
{
double retval = 0;
for (int x = 0; x < period; x++)
retval += payment / ((1.0 + fractions*rate)*Math.pow(1+rate,initialPeriods + x));
return retval;
}
Итерация ведет себя так же, как говорится в документе (9).
/**
*
* @param amount The initial amount A
* @param payment The periodic payment P
* @param payments The total number of payments n
* @param ppy The number of payment periods per year
* @param APRGuess The guess to start estimating from, 10% is 0.1, not 0.001
* @param partial Odd days, as a fraction of a pay period. 10 days of a month is 0.33333...
* @param full Full pay periods before the first payment. Usually 1.
* @return The calculated APR
*/
public double findAPRGEQ(double amount, double payment, int payments, double ppy, double APRGuess, double partial, double full)
{
double result = APRGuess;
double tempguess = APRGuess;
do
{
result = tempguess;
//Step 1
double i = tempguess/(100*ppy);
double A1 = generalEquation(payments, payment, full, partial, i);
//Step 2
double i2 = (tempguess + 0.1)/(100*ppy);
double A2 = generalEquation(payments, payment, full, partial, i2);
//Step 3
tempguess = tempguess + 0.1*(amount - A1)/(A2 - A1);
System.out.println(tempguess);
} while (Math.abs(result*10000 - tempguess*10000) > 1);
return result;
}
Обратите внимание, что, как правило, ПЛОХО использовать двойные для денежных расчетов, как я это сделал здесь, но я пишу пример SO, а не рабочий код. Кроме того, это java вместо.net, но это должно помочь вам с алгоритмом.
Я получил перевод Python (3.4) здесь. И поскольку мое приложение принимает в качестве входных данных даты, а не полные и частичные периоды платежей, я использовал их для расчета. Я сослался на документ одного из парней, который написал APRWIN OCC, и я бы посоветовал другим прочитать его, если вам нужно перевести это.
Мои тесты идут прямо из примеров Reg Z. Я еще не провел дальнейшее тестирование с APRWIN. Крайний случай, с которым мне не приходится иметь дело (поэтому я не кодировал), это когда у вас есть только 2 взноса, а первый - нерегулярный период. Проверьте документ выше, если это потенциальный вариант использования для вашего приложения. Я также не полностью проверил большинство графиков платежей, потому что мое приложение нуждается только ежемесячно и ежеквартально. Остальные просто используют примеры Reg Z.
# loan_amt: initial amount of A
# payment_amt: periodic payment P
# num_of_pay: total number of payment P
# ppy: number of payment periods per year
# apr_guess: guess to start estimating from. Default = .05, or 5%
# odd_days: odd days, meaning the fraction of a pay period for the first
# installment. If the pay period is monthly & the first installment is
# due after 45 days, the odd_days are 15/30.
# full: full pay periods before the first payment. Usually 1
# advance: date the finance contract is supposed to be funded
# first_payment_due: first due date on the finance contract
import datetime
from dateutil.relativedelta import relativedelta
def generalEquation(period, payment_amt, full, odd_days, rate):
retval = 0
for x in range(period):
retval += payment_amt / ((1.0 + odd_days * rate) * ((1 + rate) ** (
x + full)))
return retval
def _dt_to_int(dt):
"""A convenience function to change datetime objects into a day count,
represented by an integer"""
date_to_int = datetime.timedelta(days=1)
_int = int(dt / date_to_int)
return _int
def dayVarConversions(advance, first_payment_due, ppy):
"""Takes two datetime.date objects plus the ppy and returns the remainder
of a pay period for the first installment of an irregular first payment
period (odd_days) and the number of full pay periods before the first
installment (full)."""
if isinstance(advance, datetime.date) and isinstance(first_payment_due,
datetime.date):
advance_to_first = -relativedelta(advance, first_payment_due)
# returns a relativedelta object.
## Appendix J requires calculating odd_days by counting BACKWARDS
## from the later date, first subtracting full unit-periods, then
## taking the remainder as odd_days. relativedelta lets you
## calculate this easily.
# advance_date = datetime.date(2015, 2, 27)
# first_pay_date = datetime.date(2015, 4, 1)
# incorrect = relativedelta(first_pay_date, advance_date)
# correct = -relativedelta(advance_date, first_pay_date)
# print("See the difference between ", correct, " and ", incorrect, "?")
if ppy == 12:
# If the payment schedule is monthly
full = advance_to_first.months + (advance_to_first.years * 12)
odd_days = advance_to_first.days / 30
if odd_days == 1:
odd_days = 0
full += 1
# Appendix J (b)(5)(ii) requires the use of 30 in the
# denominator even if a month has 31 days, so Jan 1 to Jan 31
# counts as a full month without any odd days.
return full, odd_days
elif ppy == 4:
# If the payment schedule is quarterly
full = (advance_to_first.months // 3) + (advance_to_first.years * 4)
odd_days = ((advance_to_first.months % 3) * 30 + advance_to_first. \
days) / 90
if odd_days == 1:
odd_days = 0
full += 1
# Same as above. Sometimes odd_days would be 90/91, but not under
# Reg Z.
return full, odd_days
elif ppy == 2:
# Semiannual payments
full = (advance_to_first.months // 6) + (advance_to_first.years * 2)
odd_days = ((advance_to_first.months % 6) * 30 + advance_to_first. \
days) / 180
if odd_days == 1:
odd_days = 0
full += 1
return full, odd_days
elif ppy == 24:
# Semimonthly payments
full = (advance_to_first.months * 2) + (advance_to_first.years * \
24) + (advance_to_first.days // 15)
odd_days = ((advance_to_first.days % 15) / 15)
if odd_days == 1:
odd_days = 0
full += 1
return full, odd_days
elif ppy == 52:
# If the payment schedule is weekly, then things get real
convert_to_days = first_payment_due - advance
# Making a timedelta object
days_per_week = datetime.timedelta(days=7)
# A timedelta object equal to 1 week
if advance_to_first.years == 0:
full, odd_days = divmod(convert_to_days, days_per_week)
# Divide, save the remainder
odd_days = _dt_to_int(odd_days) / 7
# Convert odd_days from a timedelta object to an int
return full, odd_days
elif advance_to_first.years != 0 and advance_to_first.months == 0 \
and advance_to_first.days == 0:
# An exact year is an edge case. By convention, we consider
# this 52 weeks, not 52 weeks & 1 day (2 if a leap year)
full = 52 * advance_to_first.years
odd_days = 0
return full, odd_days
else:
# For >1 year, there need to be exactly 52 weeks per year,
# meaning 364 day years. The 365th day is a freebie.
year_remainder = convert_to_days - datetime.timedelta(days=(
365 * advance_to_first.years))
full, odd_days = divmod(year_remainder, days_per_week)
full += 52 * advance_to_first.years
# Sum weeks from this year, weeks from past years
odd_days = _dt_to_int(odd_days) / 7
# Convert odd_days from a timedelta object to an int
return full, odd_days
else:
print("What ppy was that?")
### Raise an error appropriate to your application
else:
print("'advance' and 'first_payment_due' should both be datetime.date objects")
def regulationZ_APR(loan_amt, payment_amt, num_of_pay, ppy, advance,
first_payment_due, apr_guess=.05):
"""Returns the calculated APR using Regulation Z/Truth In Lending Appendix
J's calculation method"""
result = apr_guess
tempguess = apr_guess + .1
full, odd_days = dayVarConversions(advance, first_payment_due, ppy)
while abs(result - tempguess) > .00001:
result = tempguess
# Step 1
rate = tempguess/(100 * ppy)
A1 = generalEquation(num_of_pay, payment_amt, full, odd_days, rate)
# Step 2
rate2 = (tempguess + 0.1)/(100 * ppy)
A2 = generalEquation(num_of_pay, payment_amt, full, odd_days, rate2)
# Step 3
tempguess = tempguess + 0.1 * (loan_amt - A1)/(A2 - A1)
return result
import unittest
class RegZTest(unittest.TestCase):
def test_regular_first_period(self):
testVar = round(regulationZ_APR(5000, 230, 24, 12,
datetime.date(1978, 1, 10), datetime.date(1978, 2, 10)), 2)
self.assertEqual(testVar, 9.69)
def test_long_first_payment(self):
testVar = round(regulationZ_APR(6000, 200, 36, 12,
datetime.date(1978, 2, 10), datetime.date(1978, 4, 1)), 2)
self.assertEqual(testVar, 11.82)
def test_semimonthly_payment_short_first_period(self):
testVar = round(regulationZ_APR(5000, 219.17, 24, 24,
datetime.date(1978, 2, 23), datetime.date(1978, 3, 1)), 2)
self.assertEqual(testVar, 10.34)
def test_semimonthly_payment_short_first_period2(self):
testVar = round(regulationZ_APR(5000, 219.17, 24, 24,
datetime.date(1978, 2, 23), datetime.date(1978, 3, 1), apr_guess=
10.34), 2)
self.assertEqual(testVar, 10.34)
def test_quarterly_payment_long_first_period(self):
testVar = round(regulationZ_APR(10000, 385, 40, 4,
datetime.date(1978, 5, 23), datetime.date(1978, 10, 1), apr_guess=
.35), 2)
self.assertEqual(testVar, 8.97)
def test_weekly_payment_long_first_period(self):
testVar = round(regulationZ_APR(500, 17.6, 30, 52,
datetime.date(1978, 3, 20), datetime.date(1978, 4, 21), apr_guess=
.1), 2)
self.assertEqual(testVar, 14.96)
class dayVarConversionsTest(unittest.TestCase):
def test_regular_month(self):
full, odd_days = dayVarConversions(datetime.date(1978, 1, 10), datetime.date(
1978, 2, 10), 12)
self.assertEqual(full, 1)
self.assertEqual(odd_days, 0)
def test_long_month(self):
full, odd_days = dayVarConversions(datetime.date(1978, 2, 10), datetime.date(
1978, 4, 1), 12)
self.assertEqual(full, 1)
self.assertEqual(odd_days, 19/30)
def test_semimonthly_short(self):
full, odd_days = dayVarConversions(datetime.date(1978, 2, 23), datetime.date(
1978, 3, 1), 24)
self.assertEqual(full, 0)
self.assertEqual(odd_days, 6/15)
def test_quarterly_long(self):
full, odd_days = dayVarConversions(datetime.date(1978, 5, 23), datetime.date(
1978, 10, 1), 4)
self.assertEqual(full, 1)
self.assertEqual(odd_days, 39/90)
def test_weekly_long(self):
full, odd_days = dayVarConversions(datetime.date(1978, 3, 20), datetime.date(
1978, 4, 21), 52)
self.assertEqual(full, 4)
self.assertEqual(odd_days, 4/7)
Хотя это старый поток, я хотел бы помочь другим избежать потери времени на это - перевод кода в PHP (или даже javascript), дает крайне неточные результаты, заставляя меня задуматься, действительно ли это работает в Java -
<?php
function generalEquation($period, $payment, $initialPeriods, $fractions, $rate){
$retval = 0;
for ($x = 0; $x < $period; $x++)
$retval += $payment / ((1.0 + $fractions*$rate)*pow(1+$rate,$initialPeriods + $x));
return $retval;
}
/**
*
* @param amount The initial amount A
* @param payment The periodic payment P
* @param payments The total number of payments n
* @param ppy The number of payment periods per year
* @param APRGuess The guess to start estimating from, 10% is 0.1, not 0.001
* @param partial Odd days, as a fraction of a pay period. 10 days of a month is 0.33333...
* @param full Full pay periods before the first payment. Usually 1.
* @return The calculated APR
*/
function findAPR($amount, $payment, $payments, $ppy, $APRGuess, $partial, $full)
{
$result = $APRGuess;
$tempguess = $APRGuess;
do
{
$result = $tempguess;
//Step 1
$i = $tempguess/(100*$ppy);
$A1 = generalEquation($payments, $payment, $full, $partial, $i);
//Step 2
$i2 = ($tempguess + 0.1)/(100*$ppy);
$A2 = generalEquation($payments, $payment, $full, $partial, $i2);
//Step 3
$tempguess = $tempguess + 0.1*($amount - $A1)/($A2 - $A1);
} while (abs($result*10000 - $tempguess*10000) > 1);
return $result;
}
// these figures should calculate to 12.5 apr (see below)..
$apr = findAPR(10000,389.84,(30*389.84),12,.11,0,1);
echo "APR: $apr" . "%";
?>
APR: 12,5000% Общая сумма финансовых сборов: 1 695,32 долл. США Финансируемая сумма: 10 000,00 долл. США Общая сумма платежей: 11 695,32 долл. США Общая сумма кредита: 10 000,00 долл. США Ежемесячный платеж: 389,84 долл. США Общая сумма процентов: 1 695,32 долл. США