class FinancialCalculator::Xnpv

Calculate the net present value of a series of unequally spaced, (potentially) unequal cashflows

Attributes

cashflows[R]

@return [Array<Numeric>] An array of cashflow amounts @api public

dates[R]

@return [Array<Date>] An array of dates on which the cashflows occur @api public

rate[R]

@return [Numeric] The rates used for calculating the present value @api public

result[R]

@return [DecNum] Result of the XNPV calculation @api public

Public Class Methods

new(rate, cashflows, dates) click to toggle source

Create a new XNPV calculation @param [Numeric] rate The discount (interest) rate @param [Array<Numeric>] cashflows An array of cashflows @param [Array<Date>] dates An array of dates representing when each cashflow occurs @return [FinancialCalculator::Xnpv] @raise [ArgumentError] When cashflows and dates are not the same length @raise [ArgumentError] When dates is not an array of Date @api public

# File lib/financial_calculator/xnpv.rb, line 45
def initialize(rate, cashflows, dates)
  validate_cashflows_dates_size(cashflows, dates)
  validate_dates(dates)

  @rate       = Flt::DecNum(rate.to_s)
  @cashflows  = cashflows.map { |cashflow| Flt::DecNum(cashflow.to_s) }.freeze
  @dates      = dates.freeze
  @result     = solve(rate, cashflows, dates)
end
with_transactions(rate, transactions) click to toggle source

Allows for creating an XNPV calculation with an array of any objects that respond to amount and date @param [Numeric] rate The discount (interest) rate @param [Array<Transaction>] transaction An array of Transaction objects. @return [FinancialCalculator::Xnpv] @api public

# File lib/financial_calculator/xnpv.rb, line 25
def self.with_transactions(rate, transactions)
  raise ArgumentError.new("Argument \"rate\" must be a Numeric. Got #{rate.class} instead") unless rate.is_a? Numeric
  unless transactions.all? { |t| t.is_a? Transaction }
    raise ArgumentError.new("Argument \"transactions\" must be an array of Transaction")
  end

  cashflows  = transactions.map(&:amount)
  dates      = transactions.map(&:date)

  self.new(rate, cashflows, dates)
end

Public Instance Methods

inspect() click to toggle source

@api public

# File lib/financial_calculator/xnpv.rb, line 56
def inspect
  "XNPV(#{result})"
end

Private Instance Methods

solve(rate, cashflows, dates) click to toggle source

Solve the NPV calcluation. @note This methods uses a 365 day year regardless of whether or not the

year is actually a leap year. This is done in order to maintain 
consistency with common implementations such as Excel and Google Sheets. 
In the future it would be nice to have a flag that allows for using 
the actual number of days in the year
# File lib/financial_calculator/xnpv.rb, line 68
def solve(rate, cashflows, dates)
  start         = dates[0]
  transactions  = cashflows.zip(dates) 
  amount_index  = 0
  date_index    = 1
  
  transactions.reduce(0) do |total, transaction|
    total += transaction[amount_index] / ((1 + @rate) ** (Flt::DecNum(transaction[date_index] - start) / 365)) 
  end
end
validate_cashflows_dates_size(cashflows, dates) click to toggle source
# File lib/financial_calculator/xnpv.rb, line 85
def validate_cashflows_dates_size(cashflows, dates)
  unless dates.length == cashflows.length
    raise ArgumentError.new("Arguments cashflows and dates must be the same length")
  end
end
validate_dates(dates) click to toggle source
# File lib/financial_calculator/xnpv.rb, line 79
def validate_dates(dates)
  unless dates.all? { |date| date.is_a? Date }
    raise ArgumentError.new("Argument dates must be an array of Date")
  end
end