Class: Finrb::Rate

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/finrb/rates.rb,
sig/finrb.rbs

Overview

the Rate class provides an interface for working with interest rates.

Constant Summary collapse

TYPES =

Accepted rate types

Returns:

  • (Hash[Symbol, String])
{ apr: 'nominal', apy: 'effective', effective: 'effective', nominal: 'nominal' }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rate, type, opts = {}) ⇒ Rate

create a new Rate instance

Examples:

create a 3.5% APR rate

Rate.new(0.035, :apr) #=> Rate(0.035, :apr)

Parameters:

  • rate (Numeric)

    the decimal value of the interest rate

  • type (Symbol)

    a valid rate type

  • opts (optional, Hash) (defaults to: {})

    set optional attributes

  • (number)
  • (Symbol)
  • (Hash[Symbol, untyped])

Options Hash (opts):

  • :duration (String)

    a time interval for which the rate is valid

  • :compounds (String) — default: :monthly

    the number of compounding periods per year

Raises:

  • (ArgumentError)

See Also:



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/finrb/rates.rb', line 71

def initialize(rate, type, opts = {})
  raise(ArgumentError, 'options must be a Hash.') unless opts.is_a?(Hash)
  raise(ArgumentError, 'options may only contain compounds and duration.') unless (opts.keys - %i[compounds duration]).empty?

  # Default monthly compounding.
  opts = { compounds: :monthly }.merge(opts)

  # Set optional attributes..
  opts.each do |key, value|
    __send__(:"#{key}=", value)
  end

  # Set the rate in the proper way, based on the value of type.
  begin
    __send__(:"#{TYPES.fetch(type)}=", Validation.decimal(rate, name: 'rate'))
  rescue KeyError
    raise(ArgumentError, "type must be one of #{TYPES.keys.join(', ')}", caller)
  end
end

Instance Attribute Details

#durationInteger

Returns the duration for which the rate is valid, in months.

Returns:

  • (Integer)

    the duration for which the rate is valid, in months



92
93
94
# File 'lib/finrb/rates.rb', line 92

def duration
  @duration
end

#effectiveFlt::DecNum

Returns the effective interest rate.

Returns:



94
95
96
# File 'lib/finrb/rates.rb', line 94

def effective
  @effective
end

#nominalFlt::DecNum

Returns the nominal interest rate.

Returns:



96
97
98
# File 'lib/finrb/rates.rb', line 96

def nominal
  @nominal
end

Class Method Details

.to_effective(rate, periods) ⇒ Flt::DecNum

convert a nominal interest rate to an effective interest rate

Examples:

Rate.to_effective(0.05, 4) #=> Flt::DecNum('0.05095')

Parameters:

  • rate (Numeric)

    the nominal interest rate

  • periods (Numeric)

    the number of compounding periods per year

  • (number)
  • (number)

Returns:



30
31
32
33
34
35
36
37
38
39
# File 'lib/finrb/rates.rb', line 30

def self.to_effective(rate, periods)
  rate = Validation.decimal(rate, name: 'rate')
  periods = compounding_periods(periods)

  if periods.infinite?
    rate.exp - 1
  else
    (((rate / periods) + 1)**periods) - 1
  end
end

.to_nominal(rate, periods) ⇒ Flt::DecNum

convert an effective interest rate to a nominal interest rate

Examples:

Rate.to_nominal(0.06, 365) #=> Flt::DecNum('0.05827')

Parameters:

  • rate (Numeric)

    the effective interest rate

  • periods (Numeric)

    the number of compounding periods per year

  • (number)
  • (number)

Returns:

See Also:



48
49
50
51
52
53
54
55
56
57
58
# File 'lib/finrb/rates.rb', line 48

def self.to_nominal(rate, periods)
  rate = Validation.decimal_greater_than(rate, minimum: -1, name: 'effective rate')

  periods = compounding_periods(periods)

  if periods.infinite?
    (rate + 1).log
  else
    periods * (((rate + 1)**(Flt::DecNum.new(1) / periods)) - 1)
  end
end

Instance Method Details

#<=>(other) ⇒ Numeric

compare two Rates, using the effective rate

Examples:

Which is better, a nominal rate of 15% compounded monthly, or 15.5% compounded semiannually?

r1 = Rate.new(0.15, :nominal) #=> Rate.new(0.160755, :apr)
r2 = Rate.new(0.155, :nominal, :compounds => :semiannually) #=> Rate.new(0.161006, :apr)
r1 <=> r2 #=> -1

Parameters:

  • other (Rate)

    the comparison Rate

  • (Rate)

Returns:



105
106
107
# File 'lib/finrb/rates.rb', line 105

def <=>(other)
  @effective <=> other.effective
end

#aprFlt::DecNum

Return the nominal annual percentage rate for the configured compounding frequency.

Returns:



111
112
113
# File 'lib/finrb/rates.rb', line 111

def apr
  nominal
end

#apyFlt::DecNum

Return the effective annual percentage yield.

Returns:

  • (Flt::DecNum)

    the effective annual percentage yield



117
118
119
# File 'lib/finrb/rates.rb', line 117

def apy
  effective
end

#inspectString

Returns:

  • (String)


153
154
155
# File 'lib/finrb/rates.rb', line 153

def inspect
  "Rate.new(#{apr.round(6)}, :apr)"
end

#monthlyFlt::DecNum

Returns the equivalent monthly effective interest rate.

Examples:

rate = Rate.new(0.15, :nominal)
rate.apr.round(6) #=> Flt::DecNum('0.15')
rate.apy.round(6) #=> Flt::DecNum('0.160755')
rate.monthly.round(6) #=> Flt::DecNum('0.0125')

Returns:

  • (Flt::DecNum)

    the equivalent monthly effective interest rate



163
164
165
# File 'lib/finrb/rates.rb', line 163

def monthly
  @monthly ||= Precision.rate(Rate.to_nominal(effective, 12) / 12)
end