Skip to content

Commit 3fefd48

Browse files
authored
add Excel actual/actual (#8)
* add Excel actual/actual * tweak docs * test values from Excel spreadsheet
1 parent 0035674 commit 3fefd48

10 files changed

Lines changed: 178 additions & 63 deletions

File tree

.travis.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ julia:
77
- 1.0
88
notifications:
99
email: false
10-
# uncomment the following lines to override the default test script
11-
#script:
12-
# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
13-
# - julia -e 'Pkg.clone(pwd()); Pkg.build("Pages"); Pkg.test("Pages"; coverage=true)'
10+
env:
11+
- PYTHON="" # needed for ExcelFiles.jl
1412

1513
jobs:
1614
include:

Manifest.toml

Lines changed: 0 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Project.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
name = "DayCounts"
22
uuid = "a3b753a0-4d35-5af2-8aa8-b76780449c56"
3-
authors = ["Eric Forgy <Eric.Forgy@coherentasia.com>"]
3+
authors = ["Eric Forgy <Eric.Forgy@coherentasia.com>", "Simon Byrne <simon@juliacomputing.com>"]
44
version = "0.0.1"
55

66
[deps]
77
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
8+
9+
[extras]
10+
ExcelFiles = "89b67f3b-d1aa-5f6f-9ca4-282e8d98620d"
811
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
12+
13+
[targets]
14+
test = ["Test", "ExcelFiles"]

docs/make.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ using Documenter
22
using DayCounts
33

44
makedocs(
5+
pages = [
6+
"index.md",
7+
"api.md",
8+
],
59
sitename = "DayCounts",
610
format = Documenter.HTML(),
711
modules = [DayCounts]

docs/src/api.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# API
2+
## Functions
3+
```@docs
4+
yearfrac
5+
```
6+
7+
## [`DayCount` types](@id daycount_types)
8+
### Actual conventions
9+
```@docs
10+
DayCounts.Actual365Fixed
11+
DayCounts.Actual360
12+
DayCounts.ActualActualISDA
13+
DayCounts.ActualActualICMA
14+
DayCounts.ActualActualExcel
15+
```
16+
17+
### 30/360 conventions
18+
```@docs
19+
DayCounts.Thirty360
20+
DayCounts.ThirtyE360
21+
DayCounts.ThirtyE360ISDA
22+
DayCounts.Thirty360Excel
23+
```

docs/src/index.md

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ DayCounts.jl provides calculations of various day count conventions used in fina
44
computing accrued interest and discount factors. These conventions have arisen to handle
55
factors such as months of different lengths and leap days.
66

7+
The primary function is the [`yearfrac`](@ref), which accepts a start date, end date and a [`DayCount` object](@ref daycount_types).
8+
79
# Example
810

911
```jldoctest
@@ -40,24 +42,20 @@ julia> yearfrac(d1, d2, DayCounts.ActualActualISDA())
4042
0.24863387978142076
4143
```
4244

45+
# Microsoft Excel compatibility
46+
47+
The [`yearfrac`](@ref) function is similar to the [Microsoft Excel `YEARFRAC` function](https://support.office.com/en-us/article/yearfrac-function-3844141e-c76d-4143-82b6-208454ddc6a8), with the exception that if `startdate` is after `enddate`, the result is negative (Excel returns the absolute value).
48+
49+
Note that some of the Excel calculations differ slightly from the specifications, in these cases we have created additional types to reproduce the behaviour.
50+
51+
| Basis | Excel Name | Equivalent `DayCount` type |
52+
|-------------|------------------|---------------------------------------|
53+
| 0 (default) | US (NASD) 30/360 | [`DayCounts.Thirty360Excel`](@ref) |
54+
| 1 | Actual/actual | [`DayCounts.ActualActualExcel`](@ref) |
55+
| 2 | Actual/360 | [`DayCounts.Actual360`](@ref) |
56+
| 3 | Actual/365 | [`DayCounts.Actual365Fixed`](@ref) |
57+
| 4 | European 30/360 | [`DayCounts.ThirtyE360`](@ref) |
58+
4359
# External Links
4460
- [_Day count convention_ on Wikipedia](https://en.wikipedia.org/wiki/Day_count_convention)
4561
- [Nasdaq Day Count Fractions](https://business.nasdaq.com/media/day-count-fractions_tcm5044-53854.pdf)
46-
47-
# Interface
48-
## Functions
49-
```@docs
50-
yearfrac
51-
```
52-
53-
## [`DayCount` types](@id daycount_types)
54-
```@docs
55-
DayCounts.Actual365Fixed
56-
DayCounts.Actual360
57-
DayCounts.ActualActualISDA
58-
DayCounts.ActualActualICMA
59-
DayCounts.Thirty360
60-
DayCounts.ThirtyE360
61-
DayCounts.ThirtyE360ISDA
62-
DayCounts.Thirty360Excel
63-
```

src/excel.jl

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
Thirty360Excel()
33
4-
**30/360* day count convention, as computed via Microsoft Excel `YEARFRAC` with the basis option of `0`.
4+
**US (NASD) 30/360** day count convention, as computed via Microsoft Excel `YEARFRAC` with the basis option of `0`.
55
66
This differs from [`Thirty360`](@ref) when:
77
* if the start date is the last day of February, then
@@ -39,3 +39,45 @@ function yearfrac(startdate::Date, enddate::Date, dc::Thirty360Excel)
3939
return thirty360(y2-y1,m2-m1,d2-d1)
4040
end
4141

42+
"""
43+
ActualActualExcel()
44+
45+
**Actual/Actual** day count convention, as computed via Microsoft Excel `YEARFRAC` with the basis option of `1`.
46+
47+
The year fraction is
48+
```math
49+
\\frac{\\text{# of days}}{D}
50+
```
51+
where:
52+
* if start date and end date are in the same calendar year, then ``D`` is the number of days in that calendar year,
53+
* otherwise if the end date is less than or equal to one year after the start date, then
54+
- ``D = 366`` if the interval includes February 29 (including both the start and end dates), or
55+
- ``D = 365`` otherwise.
56+
* otherwise it is the average length of the years included in the interval (this does not depend on where the start or end dates fall within those years).
57+
58+
# Reference
59+
- [Microsoft Excel `YEARFRAC` function](https://support.office.com/en-us/article/yearfrac-function-3844141e-c76d-4143-82b6-208454ddc6a8)
60+
- [David A. Wheeler (2008) "YEARFRAC Incompatibilities between Excel 2007 and OOXML (OXML), and the Definitions Actually Used by Excel 2007"](https://dwheeler.com/yearfrac/excel-ooxml-yearfrac.pdf)
61+
"""
62+
struct ActualActualExcel <: DayCount
63+
end
64+
function yearfrac(startdate::Date, enddate::Date, dc::ActualActualExcel)
65+
if startdate > enddate
66+
return -yearfrac(enddate, startdate, dc)
67+
end
68+
y1 = year(startdate)
69+
y2 = year(enddate)
70+
if y1 == y2
71+
return Dates.value(enddate-startdate) / daysinyear(y1)
72+
elseif startdate + Year(1) >= enddate
73+
if (isleapyear(y1) && startdate <= Date(y1,2,29)) ||
74+
(isleapyear(y2) && Date(y2,2,29) <= enddate)
75+
return Dates.value(enddate-startdate) / 366
76+
else
77+
return Dates.value(enddate-startdate) / 365
78+
end
79+
else
80+
yrange = y1:y2
81+
return Dates.value(enddate-startdate) / (sum(daysinyear, yrange) / length(yrange))
82+
end
83+
end

test/excel.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Test, DayCounts, Dates, ExcelFiles
2+
3+
@testset "Excel basis 0" begin
4+
table = load("yearfrac.xlsx","basis0")
5+
dc = DayCounts.Thirty360Excel()
6+
for r in ExcelFiles.getiterator(table)
7+
@test yearfrac(Date(r.startdate), Date(r.enddate), dc) == r.yearfrac
8+
end
9+
end
10+
11+
@testset "Excel basis 1" begin
12+
table = load("yearfrac.xlsx","basis1")
13+
dc = DayCounts.ActualActualExcel()
14+
for r in ExcelFiles.getiterator(table)
15+
@test yearfrac(Date(r.startdate), Date(r.enddate), dc) == r.yearfrac
16+
end
17+
end
18+
19+
@testset "Excel basis 2" begin
20+
table = load("yearfrac.xlsx","basis2")
21+
dc = DayCounts.Actual360()
22+
for r in ExcelFiles.getiterator(table)
23+
@test yearfrac(Date(r.startdate), Date(r.enddate), dc) == r.yearfrac
24+
end
25+
end
26+
27+
@testset "Excel basis 3" begin
28+
table = load("yearfrac.xlsx","basis3")
29+
dc = DayCounts.Actual365Fixed()
30+
for r in ExcelFiles.getiterator(table)
31+
@test yearfrac(Date(r.startdate), Date(r.enddate), dc) == r.yearfrac
32+
end
33+
end
34+
35+
@testset "Excel basis 4" begin
36+
table = load("yearfrac.xlsx","basis4")
37+
dc = DayCounts.ThirtyE360()
38+
for r in ExcelFiles.getiterator(table)
39+
@test yearfrac(Date(r.startdate), Date(r.enddate), dc) == r.yearfrac
40+
end
41+
end

test/runtests.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,45 @@ end
322322
@test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
323323
@test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
324324
end
325+
326+
327+
328+
@testset "ActualActualExcel" begin
329+
dc = DayCounts.ActualActualExcel()
330+
# calculated from Excel sheet
331+
@test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == 62/365
332+
@test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == 63/366
333+
@test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == 64/366
334+
@test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == 1523/365.3333333333333
335+
@test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == 1524/365.3333333333333
336+
@test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == 1525/365.3333333333333
337+
@test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == 29/366
338+
@test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == 28/366
339+
@test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == 27/366
340+
341+
@test yearfrac(Date(2012, 2,28), Date(2013, 2,28), dc) == 366/366
342+
@test yearfrac(Date(2012, 2,29), Date(2013, 2,28), dc) == 365/366
343+
@test yearfrac(Date(2012, 3, 1), Date(2013, 2,28), dc) == 364/365
344+
@test yearfrac(Date(2012, 2,28), Date(2013, 3, 1), dc) == 367/365.5
345+
@test yearfrac(Date(2012, 2,29), Date(2013, 3, 1), dc) == 366/365.5
346+
@test yearfrac(Date(2012, 3, 1), Date(2013, 3, 1), dc) == 365/365
347+
348+
@test yearfrac(Date(2011, 2,28), Date(2012, 2,28), dc) == 365/365
349+
@test yearfrac(Date(2011, 3, 1), Date(2012, 2,28), dc) == 364/365
350+
@test yearfrac(Date(2011, 2,28), Date(2012, 2,29), dc) == 366/365.5
351+
@test yearfrac(Date(2011, 3, 1), Date(2012, 2,29), dc) == 365/366
352+
@test yearfrac(Date(2011, 2,28), Date(2012, 3, 1), dc) == 367/365.5
353+
@test yearfrac(Date(2011, 3, 1), Date(2012, 3, 1), dc) == 366/366
354+
355+
# zeros
356+
@test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
357+
@test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
358+
@test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
359+
@test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0
360+
361+
# reflection
362+
@test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
363+
@test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
364+
end
365+
366+
include("excel.jl")

test/yearfrac.xlsx

17.4 KB
Binary file not shown.

0 commit comments

Comments
 (0)