-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
208 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,77 @@ | ||
require "rupee/mixins/find_instance" | ||
|
||
module Rupee | ||
# A class representing a day count convention used to determine cash flow | ||
# and accrual dates for fixed income products | ||
class DayCount | ||
include FindInstance | ||
|
||
autoload :THIRTY_360, "rupee/day_count/30_360" | ||
autoload :THIRTY_E_360, "rupee/day_count/30e_360" | ||
autoload :THIRTY_E_PLUS_360, "rupee/day_count/30e+_360" | ||
autoload :ACT_360, "rupee/day_count/act_360" | ||
autoload :ACT_365, "rupee/day_count/act_365" | ||
autoload :ACT_ACT, "rupee/day_count/act_act" | ||
|
||
# A description of the day count convention | ||
attr :description | ||
# The formula for determining a day count factor (days divided by years) | ||
attr :block | ||
|
||
# Create a new DayCount object | ||
def initialize(description, &block) | ||
@description = description | ||
@block = block | ||
end | ||
|
||
def factor(from, to) | ||
block.call from, to | ||
end | ||
|
||
class << self | ||
# The number of seconds in a day (a difference of <tt>1</tt> between two | ||
# dates in Ruby indicates a difference of one second) | ||
SECONDS_PER_DAY = 86_400.0 | ||
|
||
private | ||
|
||
def days(from, to) | ||
(to - from) / SECONDS_PER_DAY | ||
end | ||
|
||
def days_in_year(date) | ||
if leap_year?(date) | ||
366.0 | ||
else | ||
365.0 | ||
end | ||
end | ||
|
||
def end_of_month?(date) | ||
case date.month | ||
when 9, 4, 6, 11 | ||
# Thirty days hath September | ||
# April, June and November | ||
date.day == 30 | ||
when 1, 3, 5, 7, 8, 10, 12 | ||
# All the rest have thirty-one | ||
date.day == 31 | ||
when 2 | ||
# Save February, with twenty-eight days clear | ||
# And twenty-nine each leap year ;) | ||
date.day == (date.year % 4 == 0 && (date.year % 100 != 0 || date.year % 400 == 0)) ? 29 : 28 | ||
end | ||
end | ||
|
||
# Determines whether a date falls during a leap year. Leap years include | ||
# all years divisible by 4, with the exception of years divisible by 100 | ||
# but not divisible by 400 (got that?). That is, 2004 is a leap year, as is | ||
# 1904. But while 2000 is a leap year (divisible by 400), 1900 is not | ||
# (divisible by 100 but not 400). | ||
def leap_year?(date) # :doc: | ||
year = date.year | ||
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module Rupee | ||
class DayCount | ||
# Standard US 30/360 | ||
THIRTY_360 = DayCount.new "30/360, typical pay-fixed convention" do |from, to| | ||
m1 = from.month | ||
m2 = to.month | ||
d1 = from.day | ||
d2 = to.day | ||
|
||
if end_of_month?(from) | ||
d1 = 30 | ||
d2 = 30 if m1 == 2 && end_of_month?(to) && m2 == 2 | ||
end | ||
|
||
d2 = 30 if d2 == 31 && d1 == 30 | ||
|
||
(360 * (to.year - from.year) + 30 * (m2 - m1) + (d2 - d1)) / 360.0 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module Rupee | ||
class DayCount | ||
# 30E+/360 | ||
THIRTY_E_PLUS_360 = DayCount.new "30E+/360" do |from, to| | ||
m = to.month - from.month | ||
d1 = from.day | ||
d2 = to.day | ||
d1 = 30 if d1 == 31 | ||
if d2 == 31 | ||
m += 1 | ||
d2 = 1 | ||
end | ||
|
||
(360 * (to.year - from.year) + 30 * m + (d2 - d1)) / 360.0 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module Rupee | ||
class DayCount | ||
# Standard European 30/360 | ||
THIRTY_E_360 = DayCount.new "30E/360" do |from, to| | ||
d1 = from.day | ||
d2 = to.day | ||
d1 = 30 if end_of_month?(from) | ||
d2 = 30 if end_of_month?(to) # && (d2 != maturity_date || to.month != 2) | ||
|
||
(360 * (to.year - from.year) + 30 * (to.month - from.month) + | ||
(d2 - d1)) / 360.0 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module Rupee | ||
class DayCount | ||
# Actual/360 | ||
ACT_360 = DayCount.new "Actual/360, typical LIBOR convention" do |from, to| | ||
days(from, to) / 360.0 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module Rupee | ||
class DayCount | ||
# Actual/365 | ||
ACT_365 = DayCount.new "Actual/365" do |from, to| | ||
days(from, to) / 365.0 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module Rupee | ||
class DayCount | ||
# Actual/Actual | ||
ACT_ACT = DayCount.new "Actual/actual" do |from, to| | ||
1 - from.yday / days_in_year(from) + | ||
(to.year - from.year - 1) + | ||
to.yday / days_in_year(to) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
require File.dirname(__FILE__) + "/../spec_helper" | ||
|
||
describe DayCount do | ||
before :each do | ||
@christmas = Time.new(2011, 12, 25) | ||
@next_february = Time.new(2012, 2, 29) | ||
@many_mays = Time.new(2015, 5, 31) | ||
@tolerance = 0.0000001 | ||
end | ||
|
||
describe "30/360" do | ||
it "should produce a correct day count factor" do | ||
DayCount::THIRTY_360.factor(@christmas, @next_february).should be_within(@tolerance).of 64 / 360.0 | ||
DayCount::THIRTY_360.factor(@christmas, @many_mays).should be_within(@tolerance).of 1236 / 360.0 | ||
end | ||
end | ||
|
||
describe "30E/360" do | ||
it "should produce a correct day count factor" do | ||
DayCount::THIRTY_E_360.factor(@christmas, @next_february).should be_within(@tolerance).of 65 / 360.0 | ||
DayCount::THIRTY_E_360.factor(@christmas, @many_mays).should be_within(@tolerance).of 1235 / 360.0 | ||
end | ||
end | ||
|
||
describe "30E+/360" do | ||
it "should produce a correct day count factor" do | ||
DayCount::THIRTY_E_PLUS_360.factor(@christmas, @next_february).should be_within(@tolerance).of 64 / 360.0 | ||
DayCount::THIRTY_E_PLUS_360.factor(@christmas, @many_mays).should be_within(@tolerance).of 1236 / 360.0 | ||
end | ||
end | ||
|
||
describe "Act/360" do | ||
it "should produce a correct day count factor" do | ||
DayCount::ACT_360.factor(@christmas, @next_february).should be_within(@tolerance).of 66 / 360.0 | ||
DayCount::ACT_360.factor(@christmas, @many_mays).should be_within(@tolerance).of 1253 / 360.0 | ||
end | ||
end | ||
|
||
describe "Act/365" do | ||
it "should produce a correct day count factor" do | ||
DayCount::ACT_365.factor(@christmas, @next_february).should be_within(@tolerance).of 66 / 365.0 | ||
DayCount::ACT_365.factor(@christmas, @many_mays).should be_within(@tolerance).of 1253 / 365.0 | ||
end | ||
end | ||
|
||
describe "Act/Act" do | ||
it "should produce a correct day count factor" do | ||
DayCount::ACT_ACT.factor(@christmas, @next_february).should be_within(@tolerance).of 6 / 365.0 + 60 / 366.0 | ||
DayCount::ACT_ACT.factor(@christmas, @many_mays).should be_within(@tolerance).of 887 / 365.0 + 1 | ||
end | ||
end | ||
|
||
describe "doing leap year calculations" do | ||
it "should be correct within a normal year" | ||
it "should be correct within a leap year" | ||
end | ||
end |