2 # This file is part of SMR.
4 # SMR is free software: you can redistribute it and/or modify it under the
5 # terms of the GNU General Public License as published by the Free Software
6 # Foundation, either version 3 of the License, or (at your option) any later
9 # SMR is distributed in the hope that it will be useful, but WITHOUT ANY
10 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along with
14 # SMR. If not, see <http://www.gnu.org/licenses/>.
18 require 'cashflowitem'
20 class SecurityBond < ActiveRecord::Base
21 include Smr::HelperMethods
23 self.inheritance_column = :none
24 has_one :Security, :foreign_key=>:id_security_bond, :inverse_of=>:SecurityBond
27 # types of bond supported (may alter behaviour of some methods)
28 # NOTE: do NOT change the relations! just ADD!
29 enum type: { :fixed_rate=>0, :floating_rate=>1, :zero_rate=>2 }
32 # validates :date_maturity, :date_first_coupon, :date_last_coupon, numericality: { greater_than_or_equal_to: 0 }
35 # Returns human readable String describing :type. Lookup may be given as
36 # symbol, as string or as numerical index.
37 def SecurityBond.describe_type(lookup)
39 :fixed_rate => 'Fixed Interest Rate',
40 :floating_rate => 'Floating Interest Rate',
41 :zero_rate => 'Zero Interest Rate'
44 if lookup.is_a?(Numeric) then
45 lookup = self.types.key(lookup) || ''
47 descriptions[lookup.to_sym]
51 # Describes #type of the instance object.
53 SecurityBond.describe_type(type)
57 # Returns humanreadable String describing the model itself.
58 def SecurityBond.describe
66 # interest calculation methods supported
67 enum interest_method: {
68 :act_act=>0, :act_366=>1, :act_365=>2, :act_360=>3,
69 :_30_360=>4, :_30_365=>5, :_30_360=>6
73 # Returns human readable String describing the :interest_method. May be
74 # given as symbol, as string or as numerical index.
75 def SecurityBond.describe_interest_method(lookup)
77 :act_act => 'ICMA Rule: actual days per month and per year.',
78 :act_366 => 'Actual days per month, 366 days per year.',
79 :act_365 => 'English: actual days per month, 365 days per year.',
80 :act_360 => 'European / French: actual days per month, 360 days per year.',
81 :_30_366 => '30 days per month, 366 days per year.',
82 :_30_365 => '30 days per month, 365 days per year.',
83 :_30_360 => 'German: 30 days per month, 360 days per year.',
86 if lookup.is_a?(Numeric) then
87 lookup = self.interest_methods.key(lookup) || ''
89 descriptions[lookup.to_sym]
93 # Describes #interest_method of the instance object.
94 def describe_interest_method
95 SecurityBond.describe_interest_method(interest_method)
99 # Human readable String composed of essential properties known by a
100 # SecurityStock on its own. Useful as part to compose the name of a
104 s << '%s%%' % coupon if coupon > 0
106 if date_maturity <= 0
107 _redemption_year = 'UNDEF'
109 if redemption_installments == 1
110 _redemption_year = time_maturity.strftime('%y')
112 _redemption_year = '%s-%s' % [
113 (time_maturity - ((redemption_installments-1) * redemption_interval).months).year,
114 time_maturity.strftime('%y')
118 s << '%s(%s)' % [time_first_coupon.year, _redemption_year]
120 s << 'FLR' if type == 1
121 s << 'Zero' if type == 2
122 s << currency if not currency.blank? and currency != Smr::DEFAULT_CURRENCY
128 # Wrapper to convert integer date_* column to Time and vice versa.
129 # If :integer is given, the date_* field is updated first. The Time
130 # value is always returned.
131 def time_maturity=(string)
132 write_attribute(:date_maturity, string.to_time)
135 Time.at read_attribute(:date_maturity)
138 def time_first_coupon=(string)
139 write_attribute(:date_first_coupon, string.to_time)
141 def time_first_coupon
142 Time.at read_attribute(:date_first_coupon)
145 def time_last_coupon=(string)
146 write_attribute(:date_last_coupon, string.to_time)
149 Time.at read_attribute(:date_last_coupon)
153 # mandatory method: result based on :coupons and :quote.
154 def current_yield(quote=false)
155 quote = self.Security.last_quote unless quote
156 raise ':quote must be of Quote' if quote and not quote.is_a?(Quote)
157 if quote.quote.zero? then return false end
158 BigDecimal(coupon.to_s).as_percentage_of(quote.quote)
162 # mandatory method: YTM if all coupons and par is payed as planned until
164 # False is returned if there is no Quote or :date_maturity is UNDEF (== 0)
165 def yield_to_maturity(quote=false)
166 quote = self.Security.last_quote unless quote
167 raise ':quote must be of Quote' if quote and not quote.is_a?(Quote)
168 return false if quote.quote.zero? or date_maturity <= 0
172 # purchase at quote is a cash-outflow
173 transactions << Finance::Transaction.new(
174 denomination * (quote.quote/100) * -1 ,
178 # coupons + redemptions will flow back in
179 cashflow(:start_date=>quote.time).each do |c|
180 transactions << Finance::Transaction.new(
181 c.total, :date=>c.date
186 Percentage.new(transactions.xirr.effective)
188 # FIXME: some Security records trigger
189 # ActionView::Template::Error (Failed to reduce function values.)
190 # at the Percentage.new(...) line. Dont understand what or why?!
196 # mandatory method: True if :date is past :date_maturity
197 def is_expired?(date=Time.now)
198 true if date > time_maturity and date_maturity > 0
202 # mandatory method: sums outstanding coupons/redemptions happening before
203 # end of year, returns absolute number.
204 def cashflow_this_year(date=Time.now)
206 cashflow(:start_date=>date, :end_date=>date.end_of_year).each{|c| cf += c.total}
211 # mandatory method: return collection of Smr::CashflowItem with
212 # future/past/all cashflows created by this SecurityBond.
214 # The :amount option states the amount of Security items for which the
215 # cashflow is calculated. If not given, the smallest possible amount is
216 # used. This is :denomination in the case of bonds, one share in the case
219 # The :type option allows to filter what is returned. This model supports
220 # :none, :dividend_booking and :redemption_booking. Also see
221 # PositionRevision#types to have all types of cashflow known to
224 # In case :date_maturity is UNDEF (== 0) the cashflow is projected for 10
225 # years from :start_date.
226 def cashflow(options={:start_date=>false, :end_date=>false, :amount=>false, :type=>:none})
227 start_date = options[:start_date] || time_first_coupon
228 end_date = options[:end_date] if options[:end_date]
229 end_date = time_maturity if not end_date and date_maturity>0
230 end_date = start_date + 10.years if not end_date
232 last_coupon_payment = (date_last_coupon > 0 ? time_last_coupon : start_date + 10.years)
233 nominal_value = options[:amount] || denomination
234 filter = options[:type] || :none
235 redemptions = Array.new
239 # - backwards, from last to first
240 if date_maturity > 0 and [:none, :redemption_booking].include? filter
242 while r >= time_maturity - (redemption_installments - 1).years
243 redemptions << Smr::CashflowItem.new(
244 r, 'Redemption', nominal_value / redemption_installments,
245 :type=>:redemption_booking, :comment=>self.Security.to_s
252 if [:none, :dividend_booking].include? filter
253 # figure the next coupon
254 next_coupon = figure_next_interval_occurrence(
257 coupon_interval.months,
262 # - coupon is divided in payment intervals below one year
263 # - nominal_value is reduced by redemptions which then decreases
264 # the cashflow from that coupon
267 ri = redemptions.count>=1 ? 1 : 0
269 while c <= end_date and c <= last_coupon_payment
271 nominal_value -= r.total
272 r = redemptions[ ri+=1 ]
275 coupons << Smr::CashflowItem.new(
276 c, 'Coupon', nominal_value * (coupon / (12 / coupon_interval) / 100),
277 :type=>:dividend_booking, :comment=>self.Security.to_s
279 c += coupon_interval.months
284 # drop payments not within start_date and end_date
285 # FIXME: avoid this necessity by design, ie. when implementing caching of the entire stream
286 cf = (coupons + redemptions).delete_if{|p| (not p.date.between?(start_date, end_date)) }
291 # tell whether interest payments occur subannually
292 def has_subannual_payments?