2

我想使用二分法或割线法计算给定价格的债券到期收益率。我知道网上有 C++ 食谱,但我不知道我自己的代码有什么问题。这两种方法都会创建一个无限循环。当我尝试逐步分析变量时,迭代解决方案的成熟收益率不断增加到 80% 甚至更高。

#include <iostream>
#include <math.h>

using namespace std;

class bond {
private:
    double principal;
    double coupon;
    double timeToMaturity;

public:
    bond(double principal, double coupon, double timeToMaturity) {
        this->principal = principal;
        this->coupon = coupon;
        this->timeToMaturity = timeToMaturity;
    }

    double getprice(double YTM);
    double getytm(double price);
    double getytmsec(double price);
};

bool isZero(double x)
{
    if (fabs(x) < 1e-10)
        return true;
    return false;
}

double bond::getprice(double YTM) {
    double pvPrincipal;
    double pvCoupon;
    double factor = 0;

    pvPrincipal = this->principal / pow(1 + YTM, this->timeToMaturity);

    for (int i = 0; (this->timeToMaturity - i) > 0; i++) {
        double denom = pow(1 + YTM, this->timeToMaturity - i);
        factor += 1 / denom;
    }

    pvCoupon = this->coupon * factor;

    return pvPrincipal + pvCoupon;
}

// Bisection method
double bond::getytm(double price) {
    double low = 0;
    double high = 1;

    double f0 = getprice(low);
    double f2 = 1;
    double x2 = 0;

    while (!isZero(f2)) {
        x2 = (low + high) / 2;
        f2 = getprice(x2) - price;
        if (f2 < 0) {
            low = x2;
        }
        else {
            high = x2;
        }
    }

    return x2;
}

// Secant method
double bond::getytmsec(double price) {
    double x1 = price;
    double x2 = price + 0.25;
    double f1 = this->getprice(x1);
    double f2 = this->getprice(x2);
    for (; !isZero(f1 - price); x2 = x1, x1 = price) {
        f1 = getprice(x1);
        f2 = getprice(x2);
        price = x1 - f1 * (x1 - x2) / (f1 - f2);
    }
    return price;
}

int main() {
    bond myBond = { 1000, 25, 6 };
    cout << "YTM is " << myBond.getytm(950) << endl;
    cout << "YTM is " << myBond.getytmsec(950) << endl;

    return 0;
}
4

1 回答 1

2

如建议的那样,调试此问题的一个好方法是逐步完成计算。或者,您可以在每次迭代时打印相关值。

问题是:找到函数的零f(x) = getprice(x) - price

二分法一般是:从一个区间开始,[low, high]其中f(low)f(high)具有不同的符号(一个非正数,一个非负数)。这意味着它包含一个零。然后根据中点处的函数值选择左或右子区间以保持该属性。

在这种情况下,函数是单调且非递增的,因此我们知道它f(low)必须是较大的(非负)数,并且f(high)必须是较小的(非正)数。f(midpoint)因此,如果是负数,我们必须选择左子区间,如果是正数,我们必须选择右子区间f(midpoint)f(midpoint)但代码却相反,如果为负数,则选择正确的子区间:

    x2 = (low + high) / 2;
    f2 = getprice(x2) - price;
    if (f2 < 0) {
        low = x2;
    }

所以你最终选择越来越小的右子区间,[low, high] = [1, 1]这是一个无限循环。替换f2 < 0f2 > 0

割线法通常涉及采用两个零“估计”x_kx_{k-1}使用递归来找到更好的“估计” x_{k+1}。递归本质上使用和之间的线(x_{k-1}, f(x_{k-1})(x_k, f(x_k))并查看这条线与零相交的位置。

提供的代码有多个问题。首先,在重要的一步:

    price = x1 - f1 * (x1 - x2) / (f1 - f2);

其中x1x2是当前和以前的估计值 和f1getprice(x1)f2getprice(x2)。重要的是,请注意这f1不是我们想要零的函数f(x1)在哪里。f这不是割线公式。第二项的第一部分应该是函数的值x1,即f1 - price,而不是f1

    ... = x1 - (f1 - price) * (x1 - x2) / (f1 - f2);

其次,您将其分配给并因此丢失了您在每次迭代中确实需要price的实际值。price

第三,产量的初始猜测是priceprice + 0.25。这些与实际值相差甚远,以至于成为问题(零是产量,介于 0 和 1 之间)。尝试01

通过不混合许多问题可以避免很多这种情况。您可以从函数的实际标识中分解出找到函数零的逻辑。例如,二等分的一个步骤是:

template<typename Function>
constexpr auto bisection_step(double low, double high, Function f) -> std::pair<double, double>
{
    assert(std::isfinite(high));
    assert(std::isfinite(low));
    assert(low < high);
    assert(f(low) * f(high) <= 0.);

    auto mid = midpoint(low, high);
    if (f(low) * f(mid) <= 0.)
        return {low, mid};
    else
        return {mid, high};
}

这使您可以以断言或检查的形式指定假设,以引发异常或返回错误代码。这也使逻辑更清晰,因此不太可能选择错误的子区间。即使有人这样做,断言也会触发。

于 2020-02-04T17:50:01.363 回答