Я получил перевод на 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)