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
Returns a new instance of 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 |