在 Python 或 SQL 中使用 Excel 作为求解器

Using Excel like solver in Python or SQL

这是我在 Excel 中做的一个简单计算。我想知道它是否可以完成 python 或任何其他语言。

Loan amount 7692
Period : 12 months
Rate of interest 18 Per Annum
The formula in the B2 cell is =A1*18/100/12
The formula in the A2 cells is =A1+B2-C2

C 栏是借款人每月可能需要偿还的暂定金额。 C2 旁边的所有其他单元格仅指向 200 的第一部分。使用下图中所示的求解器后,我在 C 列中得到正确的部分 705.20。

我想知道这个计算是否可以使用任何脚本语言完成,例如 python(或 SQL)

这是最终版本的样子...

我试过类似的方法,但它没有退出循环并打印所有组合。

loan_amount= 7692
interest = 18
months =12

for rg in range(700, 710):
    for i in range(months):
        x = loan_amount * interest / 100 / 12
        y = loan_amount + x - rg
        if x < 0: 
            print rg, i
            exit
        else:
            loan_amount = y

您的 python 代码有一些问题。一方面,退出命令是 exit(),而不是 exit。这是修改后的版本:

loan_amount= 7692
interest = 18
months = 12

for rg in range(700, 710):
    y = loan_amount
    for i in range(months):
        x = y * interest / 100. / 12.
        y = y + x - rg
        if y < 0: 
            print(rg)
            exit()

这会打印出 706,这是 705.20 的最接近整数近似值。

如果您想要 python 代码准确打印 705.20,那当然可以。然而,代码会更复杂并且需要花费相当多的精力来编写。电子表格似乎更适合这项工作。

好吧,你可以用数值方法解决它(就像 Excel 那样),你可以通过在某个范围内的某个步骤检查每个数量来用蛮力解决它,或者你可以在一个分析上解决它一张纸。

使用以下表示法

L - initial loan amount = 7692
R - monthly interest rate = 1 + 0.18/12
m - number of months to repay the loan = 12
P - monthly payment to pay the loan in full after m months = unknown

is loan amount after the n-th month. is the initial loan amount (7692). m个月后的贷款金额(0)。

n个月和第(n-1)个月之间的主要关系是:

所以,解析式为:

现在用任何编程语言计算它应该是相当straight-forward。

对于给定的初始参数


顺便说一下,如果您正在模拟真实银行的运作方式,可能很难准确计算到最后一分钱。

您从上述精确解析公式中得到的答案只是近似值。

实际上,所有月度金额(付款和利息)通常都四舍五入到美分。每个月都会有一些舍入误差,这些误差会累积并增长。

除了这些舍入误差之外,不同月份的天数也不同,即使每个月的付款相同,利息通常是针对每个月的每一天计算的,因此每个月都会有所不同。然后还有闰年多一天,也会影响月息

Python 中的一种简单的蛮力方法,带有用于确定所需准确度级别的选项。

"""
    Calculate required monthly repayment for a given:
        - loan amount, and
        - annual interest rate, and
        - period of repayments in months

    You can nominate the accuracy required by adjusting the value of
        ACCURACY_AS_PARTS_OF_CENT. For example:
        - .01 = accurate to a dollar
        - .1  = accurate to 10 cents
        - 1   = accurate to cent
        - 100 = accurate to 100th of a cent
"""

# Set constants.
LOAN_AMOUNT = 7692
ANNUAL_INTEREST_PERCENT = 18
REPAY_MONTHS = 12
ACCURACY_AS_PARTS_OF_CENT = 1

loan_amount = int(LOAN_AMOUNT * 100 * ACCURACY_AS_PARTS_OF_CENT)
monthly_interest = float(ANNUAL_INTEREST_PERCENT / 100 / 12)
repay_guess_min = int((LOAN_AMOUNT / REPAY_MONTHS) - 1)
result_found = False
repayment_required = 0

for repay_guess in range(repay_guess_min, loan_amount):
    if result_found:
        break
    loan_balance = loan_amount
    for _ in range(REPAY_MONTHS):
        interest_to_add = loan_balance * monthly_interest
        loan_balance = loan_balance + interest_to_add - repay_guess
        if loan_balance <= 0:
            repayment_required = repay_guess / 100 / ACCURACY_AS_PARTS_OF_CENT
            result_found = True
            break

print('Required monthly repayment = $' + str(repayment_required))

代码:

from __future__ import print_function

"""
Formulas: http://mathforum.org/dr.math/faq/faq.interest.html
"""

def annuity_monthly_payment(P, n, q, i, debug = False):
    """
    Calculates fixed monthly annuity payment
    P   - amount of the Principal 
    n   - Number of years
    q   - the number of times per year that the interest is compounded
    i   - yearly rate of interest (for example: 0.04 for 4% interest)
    """
    if debug:
        print('P = %s\t(amount of the Principal)' %P)
        print('n = %s\t\t(# of years)' %n)
        print('q = %s\t\t(# of periods per year)' %q)
        print('i = %s %%\t(Annual interest)' %(i*100))
    return P*i/( q*(1 - pow(1 + i/q, -n*q)) )


### Given :
P = 7692
n = 1
q = 12
i = 18/100

print('M = %s' %annuity_monthly_payment(P=P, n=n, q=q, i=i, debug=True))

输出:

P = 7692        (amount of the Principal)
n = 1           (# of years)
q = 12          (# of periods per year)
i = 18.0 %      (Annual interest)
M = 705.2025054347173

我决定自己调整一下。所以他是我想出的最后一个版本,现在包括一个“amortization_tab”函数,用于打印摊销 table(PrettyTable 和 CSV 格式)和另一个有用的函数:“rest_amount”,“amount_can_be_payed_in_n_years”,“years_to_pay_off” ”。 我添加了 CSV 格式,因此可以生成初始计算,将其导入 Excel 并在那里继续。 所以现在它或多或少是 annuity loan/mortgage math.

的完整库

PS 它已经在 Python v3.5.1 上进行了测试,但它也应该 理论上 可以与另一个 Python 版本一起工作。

from __future__ import print_function
import sys
import math
import io
import csv
import prettytable

# Formulas:   http://mathforum.org/dr.math/faq/faq.interest.html

description = {
    'P': 'amount of the principal',
    'i': 'annual interest rate',
    'n': 'number of years',
    'q': 'number of times per year that the interest is compounded',
    'M': 'fixed monthly payment',
    'k': 'number of "payed" payments',
}


def pr_debug(P=None, i=None, n=None, q=None, M=None, k=None, debug=False):
    if not debug:
        return

    columns = ['var','value', 'description']
    t = prettytable.PrettyTable(columns)
    t.align['var'] = 'l'
    t.align['value'] = 'r'
    t.align['description'] = 'l'
    t.padding_width = 1
    t.float_format = '.2'

    if P:
        t.add_row(['P', P, description['P']])
    if i:
        t.add_row(['i', i, description['i']])
    if n:
        t.add_row(['n', n, description['n']])
    if q:
        t.add_row(['q', q, description['q']])
    if M:
        t.add_row(['M', M, description['M']])
    if k:
        t.add_row(['k', k, description['k']])

    print(t.get_string() + '\n')


def annuity_monthly_payment(P, n, q, i, debug = False):
    """
    Calculates fixed monthly annuity payment
    P   - amount of the principal 
    n   - number of years
    q   - number of times per year that the interest is compounded
    i   - yearly rate of interest (for example: 0.045 for 4.5% interest)
    """
    pr_debug(P=P, n=n, q=q, i=i, debug=debug)

    i /= 100
    return round(P*i/( q*(1 - pow(1 + i/q, -n*q)) ), 2)


def rest_amount(P, M, k, q, i, debug = False):
    """
    Calculates rest amount after 'k' payed payments 
    P   - Principal amount
    M   - fixed amount that have been payed 'k' times
    k   - # of payments
    q   - # of periods (12 times per year)
    i   - yearly interest rate (for example: 0.04 for 4%)
    """
    pr_debug(P=P, M=M, k=k, q=q, i=i, debug=debug)

    i /= 100
    return round((P - M*q/i) * pow(1 + i/q, k) + M*q/i, 2)


def amount_can_be_payed_in_n_years(M, n, q, i, debug = False):
    """
    Returns the amount of principal that can be paid off in n years 
    M   - fixed amount that have been payed 'k' times
    n   - Number of years
    q   - # of periods (12 times per year)
    i   - yearly interest rate (for example: 0.04 for 4%)
    """
    pr_debug(M=M, n=n, q=q, i=i, debug=debug)

    i /= 100
    return round( M*(1 - pow(1 + i/q, -n*q) )*q/i, 2)


def years_to_pay_off(P, M, q, i, debug = False):
    """
    Returns number of years needed to pay off the loan 
    P   - Principal amount
    M   - fixed amount that have been payed 'k' times
    q   - # of periods (12 times per year)
    i   - yearly interest rate (for example: 0.04 for 4%)
    """
    pr_debug(P=P, M=M, q=q, i=i, debug=debug)

    i /= 100
    return round(-math.log(1 - (P*i/M/q)) / (q*math.log(1 + i/q)), 2)


def amortization_tab(P, n, q, i, M=None, fmt='txt', debug=False):
    """
    Generates amortization table 
    P   - Principal amount
    M   - fixed amount that have been payed 'k' times
    q   - # of periods (12 times per year)
    i   - yearly interest rate (for example: 0.04 for 4%)
    """
    assert any(fmt in x for x in ['txt', 'csv'])

    # calculate monthly payment if it's not given
    if not M:
        M = annuity_monthly_payment(P=P, n=n, q=q, i=i)

    pr_debug(P=P, M=M, n=n, q=q, i=i, debug=debug)

    # column headers for the output table
    columns=['pmt#','beg_bal','pmt','interest','applied', 'end_bal']

    i /= 100

    beg_bal = P
    term = n*q

    if fmt.lower() == 'txt':
        t = prettytable.PrettyTable(columns)
        t.align = 'r'
        t.padding_width = 1
        t.float_format = '.2'
    elif fmt.lower() == 'csv':
        if sys.version_info >= (2,7,0):
            out = io.StringIO()
        else:
            out = io.BytesIO()
        t = csv.writer(out, quoting=csv.QUOTE_NONNUMERIC)
        t.writerow(columns)

    for num in range(1, term+1):
        interest = round(beg_bal*i/q , 2)
        applied = round(M - interest, 2)
        end_bal = round(beg_bal - applied,2)
        row = [num, beg_bal, M, interest, applied, end_bal]
        if fmt.lower() == 'txt':
            t.add_row(row)
        elif fmt.lower() == 'csv':
            t.writerow(row)
        beg_bal = end_bal

    if fmt.lower() == 'txt':
        return t.get_string()
    elif fmt.lower() == 'csv':
        return out.getvalue()

############################
P = 7692.0
n = 1
q = 12
i = 18
print(amortization_tab(P, n, q, i, debug=True))

print('#' * 80)
print('#' * 80)
############################
# another example
P = 100000.0
n = 5
q = 12
i = 3.5
k = 36
M = 1200
print(amortization_tab(P, n, q, i, M, fmt='csv', debug=True))
print('*' * 80)
print('Rest amount after %s payments:\t%s' %(k, rest_amount(P=P, M=M, k=k, q=q, i=i)))

输出:

+-----+---------+----------------------------------------------------------+
| var |   value | description                                              |
+-----+---------+----------------------------------------------------------+
| P   | 7692.00 | amount of the principal                                  |
| i   |      18 | annual interest rate                                     |
| n   |       1 | number of years                                          |
| q   |      12 | number of times per year that the interest is compounded |
| M   |  705.20 | fixed monthly payment                                    |
+-----+---------+----------------------------------------------------------+

+------+---------+--------+----------+---------+---------+
| pmt# | beg_bal |    pmt | interest | applied | end_bal |
+------+---------+--------+----------+---------+---------+
|    1 | 7692.00 | 705.20 |   115.38 |  589.82 | 7102.18 |
|    2 | 7102.18 | 705.20 |   106.53 |  598.67 | 6503.51 |
|    3 | 6503.51 | 705.20 |    97.55 |  607.65 | 5895.86 |
|    4 | 5895.86 | 705.20 |    88.44 |  616.76 | 5279.10 |
|    5 | 5279.10 | 705.20 |    79.19 |  626.01 | 4653.09 |
|    6 | 4653.09 | 705.20 |    69.80 |  635.40 | 4017.69 |
|    7 | 4017.69 | 705.20 |    60.27 |  644.93 | 3372.76 |
|    8 | 3372.76 | 705.20 |    50.59 |  654.61 | 2718.15 |
|    9 | 2718.15 | 705.20 |    40.77 |  664.43 | 2053.72 |
|   10 | 2053.72 | 705.20 |    30.81 |  674.39 | 1379.33 |
|   11 | 1379.33 | 705.20 |    20.69 |  684.51 |  694.82 |
|   12 |  694.82 | 705.20 |    10.42 |  694.78 |    0.04 |
+------+---------+--------+----------+---------+---------+
################################################################################
################################################################################
+-----+-----------+----------------------------------------------------------+
| var |     value | description                                              |
+-----+-----------+----------------------------------------------------------+
| P   | 100000.00 | amount of the principal                                  |
| i   |      3.50 | annual interest rate                                     |
| n   |         5 | number of years                                          |
| q   |        12 | number of times per year that the interest is compounded |
| M   |      1200 | fixed monthly payment                                    |
+-----+-----------+----------------------------------------------------------+

"pmt#","beg_bal","pmt","interest","applied","end_bal"
1,100000.0,1200,291.67,908.33,99091.67
2,99091.67,1200,289.02,910.98,98180.69
3,98180.69,1200,286.36,913.64,97267.05
4,97267.05,1200,283.7,916.3,96350.75
5,96350.75,1200,281.02,918.98,95431.77
6,95431.77,1200,278.34,921.66,94510.11
7,94510.11,1200,275.65,924.35,93585.76
8,93585.76,1200,272.96,927.04,92658.72
9,92658.72,1200,270.25,929.75,91728.97
10,91728.97,1200,267.54,932.46,90796.51
11,90796.51,1200,264.82,935.18,89861.33
12,89861.33,1200,262.1,937.9,88923.43
13,88923.43,1200,259.36,940.64,87982.79
14,87982.79,1200,256.62,943.38,87039.41
15,87039.41,1200,253.86,946.14,86093.27
16,86093.27,1200,251.11,948.89,85144.38
17,85144.38,1200,248.34,951.66,84192.72
18,84192.72,1200,245.56,954.44,83238.28
19,83238.28,1200,242.78,957.22,82281.06
20,82281.06,1200,239.99,960.01,81321.05
21,81321.05,1200,237.19,962.81,80358.24
22,80358.24,1200,234.38,965.62,79392.62
23,79392.62,1200,231.56,968.44,78424.18
24,78424.18,1200,228.74,971.26,77452.92
25,77452.92,1200,225.9,974.1,76478.82
26,76478.82,1200,223.06,976.94,75501.88
27,75501.88,1200,220.21,979.79,74522.09
28,74522.09,1200,217.36,982.64,73539.45
29,73539.45,1200,214.49,985.51,72553.94
30,72553.94,1200,211.62,988.38,71565.56
31,71565.56,1200,208.73,991.27,70574.29
32,70574.29,1200,205.84,994.16,69580.13
33,69580.13,1200,202.94,997.06,68583.07
34,68583.07,1200,200.03,999.97,67583.1
35,67583.1,1200,197.12,1002.88,66580.22
36,66580.22,1200,194.19,1005.81,65574.41
37,65574.41,1200,191.26,1008.74,64565.67
38,64565.67,1200,188.32,1011.68,63553.99
39,63553.99,1200,185.37,1014.63,62539.36
40,62539.36,1200,182.41,1017.59,61521.77
41,61521.77,1200,179.44,1020.56,60501.21
42,60501.21,1200,176.46,1023.54,59477.67
43,59477.67,1200,173.48,1026.52,58451.15
44,58451.15,1200,170.48,1029.52,57421.63
45,57421.63,1200,167.48,1032.52,56389.11
46,56389.11,1200,164.47,1035.53,55353.58
47,55353.58,1200,161.45,1038.55,54315.03
48,54315.03,1200,158.42,1041.58,53273.45
49,53273.45,1200,155.38,1044.62,52228.83
50,52228.83,1200,152.33,1047.67,51181.16
51,51181.16,1200,149.28,1050.72,50130.44
52,50130.44,1200,146.21,1053.79,49076.65
53,49076.65,1200,143.14,1056.86,48019.79
54,48019.79,1200,140.06,1059.94,46959.85
55,46959.85,1200,136.97,1063.03,45896.82
56,45896.82,1200,133.87,1066.13,44830.69
57,44830.69,1200,130.76,1069.24,43761.45
58,43761.45,1200,127.64,1072.36,42689.09
59,42689.09,1200,124.51,1075.49,41613.6
60,41613.6,1200,121.37,1078.63,40534.97

********************************************************************************
Rest amount after 36 payments:  65574.41

由于title/tag也提到了SQL,我将post一个SQL解决方案:

create table loan (
  amount decimal(10,2),
  repay_months int,
  yearly_interest_rate decimal(4, 4)
);

insert into loan values (7692, 12, 0.18);

select amount * yearly_interest_rate/12 /
           (1 - pow(1 + yearly_interest_rate/12, -repay_months))
           as monthly_payment
from   loan;

结果:

monthly_payment
-----------------
705.2025054347173

SQL Fiddle

如果您想获得整个分期还款时间表,那么一个想法是首先创建一个 table,其中包含连续的月份编号(1、2、...),足以支付最长的贷款pay-off 您将拥有以下数据的持续时间:

create table months (month int);

insert into months -- one year of months
  values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12);
-- now some record multiplication to avoid long literal lists:    
insert into months -- multiply to cover 2 years
  select month + 12 from months;
insert into months -- multiply to cover 4 years
  select month + 24 from months;
insert into months -- multiply to cover 8 years
  select month + 48 from months;
insert into months -- multiply to cover 16 years
  select month + 96 from months;
insert into months -- multiply to cover 32 years
  select month + 192 from months;
-- OK, we have now months from 1 to 384 (= 32 years)

然后使用以下查询,其中包含上述查询 sub-select:

select month,
       monthly_payment * (1 - pow(1 + monthly_interest_rate, month-repay_months)) 
                       / monthly_interest_rate
                       as loan_balance,
       monthly_payment * (1 - pow(1 + monthly_interest_rate, month-1-repay_months)) 
                       as interest,
       monthly_payment
from   months,
       (
        select amount,
               repay_months,
               yearly_interest_rate,
               yearly_interest_rate/12 as monthly_interest_rate, 
               amount * yearly_interest_rate/12 /
                   (1 - pow(1 + yearly_interest_rate/12, -repay_months))
                   as monthly_payment
        from   loan
       ) as loanX
where  month <= repay_months
order by 1;

这会产生以下输出:

+-------+--------------------+---------------------+-------------------+
| month | loan_balance       | interest            | monthly_payment   |
+-------+--------------------+---------------------+-------------------+
| 1     | 7102.177494565289  | 115.38              | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 2     | 6503.507651549055  | 106.53266241847933  | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 3     | 5895.8577608875785 |  97.55261477323582  | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 4     | 5279.093121866177  |  88.43786641331367  | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 5     | 4653.077013259458  |  79.18639682799265  | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 6     | 4017.6706630236345 |  69.79615519889187  | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 7     | 3372.7332175342744 |  60.265059945354515 | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 8     | 2718.12171036258   |  50.590998263014114 | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 9     | 2053.6910305833035 |  40.7718256554387   | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 10    | 1379.2938906073389 |  30.805365458749552 | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 11    |  694.7807935317352 |  20.689408359110082 | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+
| 12    |    0               |  10.421711902976027 | 705.2025054347173 |
+-------+--------------------+---------------------+-------------------+

这里是 SQL fiddle.

所使用的公式在this Wikipedia article中提供和导出。

import numpy as np

for pmt in np.linspace(200, 800, 20):
    loan = 7692.00
    for n in range(1, 13):
        new_balance = loan + ((loan*(1+(0.18/12)))-loan) - pmt
        loan = new_balance
    print(round(pmt, 2), '->', round(loan,2))

第一列显示 12 个月的等额付款,右列显示 12 个月后的余额。看看余额如何在 705.26 附近趋近于零?这表明零就在附近。

200.0 -> 6588.45
231.58 -> 6176.62
263.16 -> 5764.8
294.74 -> 5352.97
326.32 -> 4941.14
357.89 -> 4529.31
389.47 -> 4117.49
421.05 -> 3705.66
452.63 -> 3293.83
484.21 -> 2882.0
515.79 -> 2470.18
547.37 -> 2058.35
578.95 -> 1646.52
610.53 -> 1234.69
642.11 -> 822.86
673.68 -> 411.04
705.26 -> -0.79
736.84 -> -412.62
768.42 -> -824.45
800.0 -> -1236.27

我有一个类似的 。可能值得一试。

我认为这些 tabular/vector/matrix 类型分析非常适合 numpy 和 pandas。您通常可以编写更紧凑且易于阅读的代码。看看你是否同意。

import numpy as np
import pandas as pd

def mpmt(amt, i, nper):
    """
    Calculate the monthly payments on a loan/mortgage
    """
    i = i/12  # convert to monthly interest
    i1 = i + 1  # used multiple times in formula below
    return amt*i1**nper*i/(i1**nper-1)

def ipmt(amt, i, per, nper):
    """
    Calculate interest paid in a specific period, per, of a loan/mortgage
    """
    i = i/12  # convert to monthly interest
    i1 = i + 1  # used multiple times in formula below
    return (amt*i*(i1**(nper+1)-i1**per))/(i1*(i1**nper-1))

def amorttable(amt, i, nper):
    """
    Create an amortization table for a loan/mortgage
    """
    monthlypmt = mpmt(amt, i, nper)

    # the following calculations are vectorized
    df = pd.DataFrame({'month':np.arange(1, nper+1)})
    df['intpaid'] = ipmt(amt, i, df['month'], nper)
    df['prinpaid'] = monthlypmt - df['intpaid']
    df['balance'] = amt
    df['balance'] -= np.cumsum(df['prinpaid'])
    return df


print(amorttable(7692, .18, 12).round(2))

结果如下:

    month  intpaid  prinpaid  balance
0       1   115.38    589.82  7102.18
1       2   106.53    598.67  6503.51
2       3    97.55    607.65  5895.86
3       4    88.44    616.76  5279.09
4       5    79.19    626.02  4653.08
5       6    69.80    635.41  4017.67
6       7    60.27    644.94  3372.73
7       8    50.59    654.61  2718.12
8       9    40.77    664.43  2053.69
9      10    30.81    674.40  1379.29
10     11    20.69    684.51   694.78
11     12    10.42    694.78    -0.00