Class: Istox::Quant::Bond
- Inherits:
-
Object
- Object
- Istox::Quant::Bond
- Defined in:
- lib/istox/quant/bond.rb
Constant Summary collapse
- DEFAULT_APPROXIMATION_ERROR =
0.00001
Instance Method Summary collapse
-
#initialize(coupon: nil, maturity_date: nil, start_date: nil, coupon_frequency: nil, coupon_payment_dates: nil, face_value: 100, days_of_year: 365) ⇒ Bond
constructor
A new instance of Bond.
- #price(ytm, date, ex_coupon_date: nil, fees: 0) ⇒ Object
- #ytm(date, ex_coupon_date: nil, price: 100, fees: 0, approximation_error: DEFAULT_APPROXIMATION_ERROR) ⇒ Object
Constructor Details
#initialize(coupon: nil, maturity_date: nil, start_date: nil, coupon_frequency: nil, coupon_payment_dates: nil, face_value: 100, days_of_year: 365) ⇒ Bond
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 |
# File 'lib/istox/quant/bond.rb', line 15 def initialize(coupon: nil, maturity_date: nil, start_date: nil, coupon_frequency: nil, coupon_payment_dates: nil, face_value: 100, days_of_year: 365) raise "Invalid coupon #{coupon}" if (coupon.nil? || !is_number?(coupon)) || coupon < 0 raise "Invalid maturity_date #{maturity_date}" if (maturity_date.nil? || maturity_date.methods.include?("strftime")) raise "Invalid start_date #{start_date}" if (start_date.nil? || start_date.methods.include?("strftime")) raise "Invalid coupon_frequency #{coupon_frequency}" if (coupon_frequency.nil? || !coupon_frequency.is_a?(Integer) || coupon_frequency < 0) raise "Invalid coupon_payment_dates #{coupon_payment_dates}" if (coupon_payment_dates.nil? || (coupon_payment_dates.count == 0 && coupon_frequency != 0) || coupon_payment_dates.any? { |date| date > maturity_date.to_date }) raise "Invalid days_of_year #{days_of_year}" if (days_of_year != 365 && days_of_year != 360) raise "start_date is not before maturity_date" if start_date>=maturity_date @coupon = coupon.to_d @maturity_date = maturity_date.to_date @coupon_frequency = coupon_frequency.to_i # if this is 0, it means zero coupon @days_of_year = days_of_year.to_d @face_value = face_value.to_d @coupon_payment_dates = coupon_payment_dates.map(&:to_date).uniq.sort # note here we work out the start date based on maturity date and nunber of years @start_date = start_date.to_date @pay_accrued_interest = false @coupon_payment_dates_include_accrued_interest = false if !is_zero_coupon? if @coupon_payment_dates.include?(@maturity_date) # maturity date is a coupon payment date, check if this should # be accrued interest or last normal coupon if @coupon_payment_dates.count > 1 previous_coupon_date = @coupon_payment_dates[@coupon_payment_dates.count-2] next_coupon_date = add_month(previous_coupon_date, -(12/@coupon_frequency).to_i) # If maturity date is a normal coupon payment, the theorecical next_coupon_date # calculated from previous coupon payment should be the maturity date, to be safe, # we allow 3 days difference if (next_coupon_date - @maturity_date).abs <= 3 @pay_accrued_interest = false @coupon_payment_dates_include_accrued_interest = false else @pay_accrued_interest = true @coupon_payment_dates_include_accrued_interest = true end else # maturity date is only coupon payment date, shouldn't be accrued interest! @pay_accrued_interest = false @coupon_payment_dates_include_accrued_interest = false end else # maturity date is not included in coupon payment date, consider # this needs to pay accrued interest @pay_accrued_interest = true @coupon_payment_dates_include_accrued_interest = false end end log.info "Bond info: start_date=#{@start_date} maturity_date=#{@maturity_date} days_of_years=#{days_of_year} coupon=#{@coupon} coupon_frequency=#{coupon_frequency} face_value=#{@face_value} coupon_payment_dates=#{@coupon_payment_dates} pay_accrued_interest=#{@pay_accrued_interest} coupon_payment_dates_include_accrued_interest=#{@coupon_payment_dates_include_accrued_interest}" end |
Instance Method Details
#price(ytm, date, ex_coupon_date: nil, fees: 0) ⇒ Object
68 69 70 71 72 73 |
# File 'lib/istox/quant/bond.rb', line 68 def price(ytm, date, ex_coupon_date: nil, fees: 0) irr = ytm irr = ytm/@coupon_frequency if !is_zero_coupon? price = price_for_irr(irr, date, ex_coupon_date: ex_coupon_date, fees: fees) price end |
#ytm(date, ex_coupon_date: nil, price: 100, fees: 0, approximation_error: DEFAULT_APPROXIMATION_ERROR) ⇒ Object
75 76 77 78 79 80 81 82 83 |
# File 'lib/istox/quant/bond.rb', line 75 def ytm(date, ex_coupon_date: nil, price: 100, fees: 0, approximation_error: DEFAULT_APPROXIMATION_ERROR) if !is_zero_coupon? && price == @face_value && date <= @start_date # for non zero coupon bond, if price is face value and date is at or before start date # YTM is simply the coupon rate return @coupon end ytm_down, ytm_up = ytm_limits(price, date, ex_coupon_date: ex_coupon_date, fees: fees) approximate_ytm(ytm_down, ytm_up, price, date, ex_coupon_date: ex_coupon_date, fees: fees, approximation_error: approximation_error) end |