Я пытаюсь использовать метод Бренца для расчета внутренней нормы доходности. Используя это в качестве шаблона Метод Брента
В настоящее время у меня есть этот код:
(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)