使用 Reg Z 附录 J 计算年利率

发布于 2025-01-06 18:31:56 字数 751 浏览 6 评论 0原文

好的。我是这个网站的新手,所以“大家好”!上周我一直在努力解决一个难题,如果您能给我任何帮助,我将不胜感激。

我知道有很多公式可以计算年利率,但我测试了很多公式,它们不能正确处理封闭式(消费贷款)的奇数日。政府试图通过发布其真实借贷法案的附录 J 来为我们这些凡人提供一些帮助。 可以在这里找到:https://www.fdic.gov/regulations /laws/rules/6500-3550.html

如果您足够勇敢(!!),您可以看到他们提供的公式,该公式将解决 APR,包括 贷款。奇数日是贷款开始时未真正包含在定期付款中但仍收取利息的日子。例如,您于 01/20/2012 借了一笔 1,000.00 美元的贷款,您的第一笔付款日期为 03/01/2012。从 01/20/2012 到 01/30/2012 有 10 个奇数日。所有月份的计算都是 30 天。

我希望有一个在微积分方面具有重要背景的人能够解释附录 J 中的公式,并解释他们用来求解这些公式的精算方法。我理解迭代过程。我首先尝试使用 Newton-Raphson 方法来解决这个问题,但我的 APR 公式没有考虑奇数天。它在没有奇数日的不太简单的情况下效果很好,但在奇数日时却很困难。

我知道阅读这份文档是非常困难的!我已经取得了一些进展,但有些事情我就是不明白他们是怎么做的。他们似乎神奇地介绍了一些东西。

无论如何,提前感谢您的帮助! :)

OK. I'm brand new to this site so "Hello All"! Well I've been wrestling with a difficult problem for the last week and I would appreciate any help you can give me.

I know there are many formulas out there to calculate APR but I've tested many formulas and they do not handle Odd-Days properly for closed-end (consumer loans). The government has attempted to give us mere mortals some help with this by publishing an Appendix J to their truth-in-lending act.
It can be found here: https://www.fdic.gov/regulations/laws/rules/6500-3550.html

If you're brave (!!), you can see the formulas they provide which will solve for the APR, including the Odd-Days of the loan. Odd-Days are the days at the beginning of the loan that isn't really covered by a regular period payment but interest is still being charged. For example, you take a loan for $1,000.00 on 01/20/2012 and your first payment is 03/01/2012. You have 10 odd-days from 01/20/2012 to 01/30/2012. All months are 30 days for their calcs.

What I'm hoping for is someone with a significant background in Calculus who can interpret the the formulas you'll find about half way down Appendix J. And interpret the Actuarial method they're using to solve these formulas. I understand the iterative process. I first tried to solve this using the Newton-Raphson method but my formula for the APR did not account for the Odd-days. It worked great in the unlikely trivial case where there are no odd days, but struggled with odd-days.

I know that reading this document is very difficult! I've made some headway but there are certain things I just can't figure out how they're doing. They seem to introduce a few things as if by magic.

Anyways thanks ahead of time for helping! :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

同尘 2025-01-13 18:31:56

好吧,您并不是在开玩笑说该文档有点难以阅读。不过,该解决方案实际上并没有那么糟糕,具体取决于实施情况。我多次尝试使用他们的各种简化论坛,但都失败了,最终使用顶部的通用公式(8)得到了它。从技术上讲,这是一种简化。实际的通用公式将采用长度为period的数组作为其他参数,并在循环中使用它们的索引。您可以使用此方法获取迭代步骤的 A' 和 A''。奇数天由 (1.0 +fractions*rate) 处理,在文档中显示为 1 + fi。利率是每个时期的利率,而不是全年的利率。

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; 
}

请注意,作为一般规则,像我在这里所做的那样使用 double 进行货币计算是不好的,但我正在编写一个 SO 示例,而不是生产代码。另外,它是 java 而不是 .net,但它应该可以帮助您了解算法。

Alright, you weren't kidding about the document being a bit hard to read. The solution is actually not that bad though, depending on implementation. I failed repeatedly trying to use their various simplified forumulae and eventually got it using the general formula up top(8). Technically this is a simplification. The actual general formula would take arrays of length period for the other arguments and use their indexes in the loop. You use this method to get A' and A'' for the iteration step. Odd days are handled by (1.0 + fractions*rate), which appears as 1 + f i in the document. Rate is the rate per period, not overall apr.

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;
}

Iteration behaves just as the document says in its example(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; 
}

Note that as a general rule it is BAD to use double for monetary calculations as I have done here, but I'm writing a SO example, not production code. Also, it's java instead of .net, but it should help you with the algorithm.

落墨 2025-01-13 18:31:56

虽然这是一个旧线程,但我想帮助其他人避免在这方面浪费时间 - 将代码翻译为 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 美元

Tho this is an old thread, I'd like to help others avoid wasting time on this - translating the code to PHP (or even javascript), gives wildly inaccurate results, causing me to wonder if it really worked in 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%
Total Financial Charges: $1,695.32
Amount Financed: $10,000.00
Total Payments: $11,695.32
Total Loan: $10,000.00
Monthly Payment: $389.84
Total Interest: $1,695.32

孤单情人 2025-01-13 18:31:56

我在这里得到了 Python (3.4) 翻译。由于我的应用程序将日期作为输入,而不是全部和部分付款期限,因此我引入了一种计算这些日期的方法。我引用了 OCC APRWIN 编写者之一的文档,并且我如果您需要重新翻译,建议其他人阅读它。

我的测试直接来自 Reg Z 示例。我还没有对 APRWIN 进行进一步的测试。我不必处理(因此没有编码)的一个边缘情况是,当您只有两期付款且第一期是不规则期间时。如果这是您的应用程序的潜在用例,请检查上面的文档。我还没有完全测试大部分付款计划,因为我的应用程序只需要每月和每季度。其余的只是使用 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)

I got me a Python (3.4) translation here. And since my application takes dates as inputs, not full and partial payment periods, I threw in a way to calculate those. I referenced a document by one of the guys that wrote the OCC's APRWIN, and I'd advise others to read it if you need to re-translate this.

My tests come straight from the Reg Z examples. I haven't done further testing with APRWIN yet. An edge case I don't have to deal with (so haven't coded for) is when you only have 2 installments and the first is an irregular period. Check the document above if that's a potential use case for your app. I also haven't fully tested most of the payment schedules because my app only needs monthly and quarterly. The rest are just there to use Reg Z's examples.

# 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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文