1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright 2018 Chandra Sekar S
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # XIRR
//!
//! `xirr` implements the XIRR function found in spreadsheet applications like LibreOffice Calc.
//!
//! # Example
//!
//! ```
//! use xirr::*;
//!
//! let payments = vec![
//!     Payment { date: "2015-06-11".parse().unwrap(), amount: -1000.0 },
//!     Payment { date: "2015-07-21".parse().unwrap(), amount: -9000.0 },
//!     Payment { date: "2018-06-10".parse().unwrap(), amount: 20000.0 },
//!     Payment { date: "2015-10-17".parse().unwrap(), amount: -3000.0 },
//! ];
//!
//!  assert_eq!(0.1635371584432641, compute(&payments).unwrap());
//! ```

use chrono::prelude::*;
use std::error::Error;
use std::fmt;
use std::fmt::{Display, Formatter};

const MAX_ERROR: f64 = 1e-10;
const MAX_COMPUTE_WITH_GUESS_ITERATIONS: u32 = 50;

/// A payment made or received on a particular date.
///
/// `amount` must be negative for payment made and positive for payment received.
#[derive(Copy, Clone)]
pub struct Payment {
    pub amount: f64,
    pub date: NaiveDate,
}

/// Calculates the internal rate of return of a series of irregular payments.
///
/// It tries to identify the rate of return using Newton's method with an initial guess of 0.1.
/// If that does not provide a solution, it attempts with guesses from -0.99 to 0.99
/// in increments of 0.01 and returns NaN if that fails too.
///
/// # Errors
///
/// This function will return [`InvalidPaymentsError`](struct.InvalidPaymentsError.html)
/// if both positive and negative payments are not provided.
pub fn compute(payments: &Vec<Payment>) -> Result<f64, InvalidPaymentsError> {
    validate(payments)?;

    let mut sorted: Vec<_> = payments.iter().collect();
    sorted.sort_by_key(|p| p.date);

    let mut rate = compute_with_guess(&sorted, 0.1);
    let mut guess = -0.99;

    while guess < 1.0 && (rate.is_nan() || rate.is_infinite()) {
        rate = compute_with_guess(&sorted, guess);
        guess += 0.01;
    }

    Ok(rate)
}

/// An error returned when the payments provided to [`compute`](fn.compute.html) do not contain
/// both negative and positive payments.
#[derive(Debug)]
pub struct InvalidPaymentsError;

impl Display for InvalidPaymentsError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        "negative and positive payments are required".fmt(f)
    }
}

impl Error for InvalidPaymentsError {}

fn compute_with_guess(payments: &Vec<&Payment>, guess: f64) -> f64 {
    let mut r = guess;
    let mut e = 1.0;

    for _ in 0..MAX_COMPUTE_WITH_GUESS_ITERATIONS {
        if e <= MAX_ERROR {
            return r;
        }

        let r1 = r - xirr(payments, r) / dxirr(payments, r);
        e = (r1 - r).abs();
        r = r1;
    }

    f64::NAN
}

fn xirr(payments: &Vec<&Payment>, rate: f64) -> f64 {
    let mut result = 0.0;
    for p in payments {
        let exp = get_exp(p, payments[0]);
        result += p.amount / (1.0 + rate).powf(exp)
    }
    result
}

fn dxirr(payments: &Vec<&Payment>, rate: f64) -> f64 {
    let mut result = 0.0;
    for p in payments {
        let exp = get_exp(p, payments[0]);
        result -= p.amount * exp / (1.0 + rate).powf(exp + 1.0)
    }
    result
}

fn validate(payments: &Vec<Payment>) -> Result<(), InvalidPaymentsError> {
    let positive = payments.iter().any(|p| p.amount > 0.0);
    let negative = payments.iter().any(|p| p.amount < 0.0);

    if positive && negative {
        Ok(())
    } else {
        Err(InvalidPaymentsError)
    }
}

fn get_exp(p: &Payment, p0: &Payment) -> f64 {
    (p.date - p0.date).num_days() as f64 / 365.0
}