Расчет APR с использованием Reg Z Приложение J - PullRequest
4 голосов
/ 17 февраля 2012

OK. Я новичок в этом сайте, так что "Привет всем"! Ну, я боролся с трудной проблемой на прошлой неделе, и я был бы признателен за любую помощь, которую вы можете мне дать.

Я знаю, что есть много формул для расчета APR, но я протестировал много формул, и они не обрабатывают нечетные дни должным образом для закрытых (потребительские кредиты). Правительство попыталось помочь нам, простым смертным, помочь с этим, опубликовав Приложение J к их закону о предоставлении правды. Его можно найти здесь: https://www.fdic.gov/regulations/laws/rules/6500-3550.html

Если вы храбры (!!), вы можете увидеть формулы, которые они предоставляют, которые решат для АТР, включая нечетные дни кредита. Нечетные дни - это дни в начале займа, которые на самом деле не покрываются регулярным платежом, но проценты по-прежнему начисляются. Например, вы берете кредит на 1 000,00 долларов США 20 января 2012 года, а ваш первый платеж - 3 марта 2012 года. У вас есть 10 с лишним дней с 20.01.2012 по 30.01.2012. Все месяцы составляют 30 дней для их расчетов.

Я надеюсь, что кто-то со значительным опытом работы в Calculus сможет интерпретировать формулы, которые вы найдете примерно на полпути вниз в Приложении J. И интерпретировать актуарный метод, который они используют для решения этих формул. Я понимаю итеративный процесс. Сначала я попытался решить эту проблему, используя метод Ньютона-Рафсона, но моя формула для APR не учитывала нечетные дни. Он отлично работал в маловероятном тривиальном случае, когда не было странных дней, но боролся с нечетными днями.

Я знаю, что читать этот документ очень сложно! Я добился определенных успехов, но есть определенные вещи, которые я просто не могу понять, как у них дела. Кажется, они вводят некоторые вещи, как по волшебству.

В любом случае, спасибо заранее за помощь! :)

Ответы [ 3 ]

5 голосов
/ 17 февраля 2012

Хорошо, вы не шутите, что документ немного сложен для чтения.Решение на самом деле не так уж и плохо, в зависимости от реализации.Я неоднократно терпел неудачу, пытаясь использовать их различные упрощенные форумы, и в конце концов получил его, используя общую формулу вверх (8).Технически это упрощение.Фактическая общая формула будет принимать массивы длины period для других аргументов и использовать их индексы в цикле.Вы используете этот метод, чтобы получить A и A для шага итерации.Нечетные дни обрабатываются (1.0 + fractions*rate), который отображается в документе как 1 + f i.Rate - это ставка за период, а не в целом за апрель.

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).ПЛОХО использовать двойные для денежных расчетов, как я это сделал здесь, но я пишу SO пример, а не рабочий код.Кроме того, это java вместо .net, но он должен помочь вам с алгоритмом.

0 голосов
/ 13 июня 2015

Я получил перевод на 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)
0 голосов
/ 11 сентября 2014

То есть это старый поток, я хотел бы помочь другим избежать потери времени на это - перевод кода в 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" . "%";
?>

апреля: 12,5000% Общая сумма финансовых расходов: 1 695,32 долл. США Финансируемая сумма: $ 10 000,00 Общая сумма платежей: $ 11 695,32 Общая сумма кредита: 10 000,00 долларов США Ежемесячный платеж: $ 389,84 Общий интерес: $ 1 695,32

...