-
Notifications
You must be signed in to change notification settings - Fork 134
/
Copy pathDayCounter.py
146 lines (98 loc) · 3.91 KB
/
DayCounter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# -*- coding: utf-8 -*-
u"""
Created on 2017-6-11
@author: cheng.li
"""
from PyFin.DateUtilities.Period import Period
from PyFin.Enums.TimeUnits import TimeUnits
from PyFin.Enums.Months import Months
class DayCounter(object):
def __init__(self, dcName):
dcName = dcName.lower()
if dcName in _dcDict:
self.impl_ = _dcDict[dcName]()
else:
raise ValueError('{0} is not a valid day counter name'.format(dcName))
def name(self):
return self.impl_.name()
def dayCount(self, d1, d2):
return self.impl_.dayCount(d1, d2)
def yearFraction(self, d1, d2, refPeriodStart=None, refPeriodEnd=None):
return self.impl_.yearFraction(d1, d2, refPeriodStart, refPeriodEnd)
class DayCounterImpl(object):
def name(self):
pass
def dayCount(self, d1, d2):
return d2 - d1
def yearFraction(self, d1, d2, refPeriodStart, refPeriodEnd):
pass
class Actual360(DayCounterImpl):
def name(self):
return 'Actual/360'
def yearFraction(self, d1, d2, refPeriodStart, refPeriodEnd2):
return float(d2 - d1) / 360.
class Actual365Fixed(DayCounterImpl):
def name(self):
return 'Actual/365 (Fixed)'
def yearFraction(self, d1, d2, refPeriodStart, refPeriodEnd2):
return float(d2 - d1) / 365.
class Actual365NoLeap(DayCounterImpl):
MonthOffset = [
0, 31, 59, 90, 120, 151, # Jan - Jun
181, 212, 243, 273, 304, 334 # Jun - Dec
]
def name(self):
return 'Actual/365 (NL)'
def dayCount(self, d1, d2):
s1 = d1.dayOfMonth() + self.MonthOffset[d1.month() - 1] + (d1.year() * 365)
s2 = d2.dayOfMonth() + self.MonthOffset[d2.month() - 1] + (d2.year() * 365)
if d1.month() == Months.Feb and d1.dayOfMonth() == 29:
s1 -= 1
if d2.month() == Months.Feb and d2.dayOfMonth() == 29:
s2 -= 1
return s2 - s1
def yearFraction(self, d1, d2, refPeriodStart, refPeriodEnd2):
return self.dayCount(d1, d2) / 365.
class ActualActualISMAImpl(DayCounterImpl):
def name(self):
return 'Actual/Actual (ISMA)'
def yearFraction(self, d1, d2, d3, d4):
if d1 == d2:
return 0.
if d1 > d2:
return -self.yearFraction(d2, d1, d3, d4)
refPeriodStart = d3 if d3 else d1
refPeriodEnd = d4 if d4 else d2
months = int(0.5 + 12. * (refPeriodEnd - refPeriodStart) / 365.)
if months == 0:
refPeriodStart = d1
refPeriodEnd = d1 + '1y'
months = 12
period = months / 12.
if d2 <= refPeriodEnd:
if d1 >= refPeriodStart:
return period * (d2 - d1) / (refPeriodEnd - refPeriodStart)
else:
previousRef = refPeriodStart - Period(length=months, units=TimeUnits.Months)
if d2 > refPeriodStart:
return self.yearFraction(d1, refPeriodStart, previousRef, refPeriodStart) \
+ self.yearFraction(refPeriodStart, d2, refPeriodStart, refPeriodEnd)
else:
return self.yearFraction(d1, d2, previousRef, refPeriodStart)
else:
sum = self.yearFraction(d1, refPeriodEnd, refPeriodStart, refPeriodEnd)
i = 0
while True:
newRefStart = refPeriodEnd + Period(length=i*months, units=TimeUnits.Months)
newRefEnd = refPeriodEnd + Period(length=(i+1)*months, units=TimeUnits.Months)
if d2 < newRefEnd:
break
else:
sum += period
i += 1
sum += self.yearFraction(newRefStart, d2, newRefStart, newRefEnd)
return sum
_dcDict = {'actual/360': Actual360,
'actual/365 (fixed)': Actual365Fixed,
'actual/365 (nl)': Actual365NoLeap,
'actual/actual (isma)': ActualActualISMAImpl}