Реализация метода Брента в Clojure для определения IRR - PullRequest
0 голосов
/ 17 июня 2020

Я пытаюсь использовать метод Бренца для расчета внутренней нормы доходности. Используя это в качестве шаблона Метод Брента

В настоящее время у меня есть этот код:

(defn discount-factor
  [discount-rate n]
  (/ 1 (utils/exponent (+ 1 (/ discount-rate 100)) n)))

(defn-spec simple-present-value float?
  "Takes a `future-value` and discounts it with `n` years in the future. Uses a `discount-rate` in percent.
   Returns the discounted present value"
  [future-value number?, n int?, discount-rate ::discount-rate]

  (utils/round (* future-value (discount-factor discount-rate n))))

(defn-spec discount-cashflow float?
  "Takes collection of `cash-flow` starting from year 0. Discounts each year with the `discount-rate`. Returns present value"
  [cashflow ::cashflow, discount-rate ::discount-rate]

  (if (empty? cashflow)
    0.0
    (->> (map (fn [element index]
                (simple-present-value element index discount-rate))
              cashflow
              (range (count cashflow)))
         (reduce +)
         utils/round)))

(def tolerance 0.001)

(defn inverse-quadratic-interpolation [a b c fa fb fc]
  (+ (/ (* a fb fc)
        (* (- fa fb) (- fa fc)))
     (/ (* b fa fc)
        (* (- fb fa) (- fb fc)))
     (/ (* c fa fb)
        (* (- fc fa) (- fc fb)))))

(defn secant-method [a b fa fb]
  (- b (* fb (/ (- b a)
                (- fb fa)))))

(defn bisection-method? [a b c fa fb fc s mflag d]
  (or (not (<= (/ (+ (* 3 a) b)
                  4)
               s b))
      (and mflag (>= (absolute (- s b)) (/ (absolute (- b c)) 2)))
      (and (not mflag) (>= (absolute (- s b)) (/ (absolute (- c d)) 2)))
      (and mflag (< (absolute (- b c)) tolerance))
      (and (not mflag) (< (absolute (- c d)) tolerance))))

(defn bisection [a b]
  (/ (+ a b)
     2))

(defn calculate-s [a b c fa fb fc mflag d]
  (let [s (if (and (not= fa fc) (not= fb fc))
            (inverse-quadratic-interpolation a b c fa fb fc)
            (secant-method a b fa fb))]
    (if (bisection-method? a b c fa fb fc s mflag d)
      {:s (bisection a b)
       :mflag true}
      {:s s
       :mflag false})))

(defn brents-method [a b f]
  (let [test-fa (f a)
        test-fb (f b)
        initial-fa (if (< (absolute test-fa) (absolute test-fb)) test-fb test-fa)
        initial-fb (if (< (absolute test-fa) (absolute test-fb)) test-fa test-fb)
        initial-a (if (< (absolute test-fa) (absolute test-fb)) b a)
        initial-b (if (< (absolute test-fa) (absolute test-fb)) a b)]

    (loop [a initial-a b initial-b c initial-a
           fa initial-fa fb initial-fb fc initial-fa
           mflag true
           d nil]

      (let [{s :s mflag :mflag} (calculate-s a b c fa fb fc mflag d)
            fs (f s)]

        (cond (or (= fb 0) (= fs 0) (< (absolute (- b a)) tolerance))
              b
              (and (< (* fa fs) 0) (< (absolute fa) (absolute fb)))
              (recur s a b (f s) fa fb mflag c)
              (and (>= (* fa fs) 0) (< (absolute fa) (absolute fb)))
              (recur b s b fb (f s) fb mflag c)

              (and (< (* fa fs) 0) (>= (absolute fa) (absolute fb)))
              (recur a s b fa (f s) fb mflag c)
              (and (>= (* fa fs) 0) (>= (absolute fa) (absolute fb)))
              (recur s b b (f s) fb fb mflag c))))))



(defn-spec internal-rate-of-return ::discount-rate
  [cashflow sequential?]
  (brents-method 100 10 (partial discount-cashflow cashflow)))

Кажется, он не может рассчитать правильный irr. Я либо получаю; 1. что-то близкое

Expected:
66.19
Actual:
66.35271788727553
ошибка деления на ноль в этом разделе:
(defn secant-method [a b fa fb]
  (- b (* fb (/ (- b a)
                (- fb fa)))))

Это сложная проблема, поэтому любая помощь будет принята с благодарностью. Код не особо функциональный, учитывая мой шаблон.

Тесты:

       (fact "Calculates correct internal rate of return of a cashflow"
             (finance/discount-rate :cashflow [-10 10 11]) => 66.19)
       (fact "Calculates correct internal rate of return of a cashflow large numbers"
             (finance/discount-rate :cashflow [-1000 1000 1100]) => 66.19)
       (fact "Calculates correct internal rate of return of a cashflow long lasting cashflows"
             (finance/discount-rate :cashflow [-1000 1000 1100 1100 1100 1100 1100 1100 1100 1100]) => 104.71)
...