rlquantdata 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rlquantdata might be problematic. Click here for more details.
- rlquantdata-0.1.0.dist-info/METADATA +24 -0
- rlquantdata-0.1.0.dist-info/RECORD +22 -0
- rlquantdata-0.1.0.dist-info/WHEEL +5 -0
- rlquantdata-0.1.0.dist-info/licenses/LICENSE +21 -0
- rlquantdata-0.1.0.dist-info/top_level.txt +1 -0
- src/__init__.py +0 -0
- src/rlquantdata/DateUtilities/Asserts.py +26 -0
- src/rlquantdata/DateUtilities/Calendar.py +282 -0
- src/rlquantdata/DateUtilities/Date.py +427 -0
- src/rlquantdata/DateUtilities/DateUtilities.py +86 -0
- src/rlquantdata/DateUtilities/Date_enum.py +47 -0
- src/rlquantdata/DateUtilities/DayCounter.py +141 -0
- src/rlquantdata/DateUtilities/Period.py +319 -0
- src/rlquantdata/DateUtilities/Schedule.py +309 -0
- src/rlquantdata/DateUtilities/Settings.py +39 -0
- src/rlquantdata/DateUtilities/__init__.py +24 -0
- src/rlquantdata/__init__.py +0 -0
- src/rlquantdata/client.py +665 -0
- src/rlquantdata/utilities/__init__.py +0 -0
- src/rlquantdata/utilities/trade_date_utils.py +65 -0
- src/rlquantdata/utilities/utils.py +197 -0
- src/rlquantdata/version.py +1 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from math import floor
|
|
5
|
+
|
|
6
|
+
from src.rlquantdata.DateUtilities.Date_enum import TimeUnits
|
|
7
|
+
from src.rlquantdata.DateUtilities.Asserts import require, ensureRaise
|
|
8
|
+
|
|
9
|
+
_unitPattern = re.compile('[BbDdMmWwYy]{1}')
|
|
10
|
+
_numberPattern = re.compile('[-+]*[0-9]+')
|
|
11
|
+
|
|
12
|
+
_unitsDict = {'d': TimeUnits.Days,
|
|
13
|
+
'b': TimeUnits.BDays,
|
|
14
|
+
'w': TimeUnits.Weeks,
|
|
15
|
+
'm': TimeUnits.Months,
|
|
16
|
+
'y': TimeUnits.Years}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Period(object):
|
|
20
|
+
|
|
21
|
+
def __init__(self, reprStr=None, length=0, units=0):
|
|
22
|
+
|
|
23
|
+
n: int
|
|
24
|
+
unitsStr: str
|
|
25
|
+
|
|
26
|
+
if reprStr:
|
|
27
|
+
unitsPos = _unitPattern.search(reprStr)
|
|
28
|
+
numPos = _numberPattern.search(reprStr)
|
|
29
|
+
unitsStr = reprStr[unitsPos.start():unitsPos.end()].lower()
|
|
30
|
+
n = int(reprStr[numPos.start():numPos.end()])
|
|
31
|
+
self._length = n
|
|
32
|
+
self._units = _unitsDict[unitsStr]
|
|
33
|
+
else:
|
|
34
|
+
self._length = length
|
|
35
|
+
self._units = units
|
|
36
|
+
|
|
37
|
+
def length(self):
|
|
38
|
+
return self._length
|
|
39
|
+
|
|
40
|
+
def units(self):
|
|
41
|
+
return self._units
|
|
42
|
+
|
|
43
|
+
def normalize(self):
|
|
44
|
+
length = self.length()
|
|
45
|
+
units = self.units()
|
|
46
|
+
if length != 0:
|
|
47
|
+
if units == TimeUnits.BDays or units == TimeUnits.Weeks or units == TimeUnits.Years:
|
|
48
|
+
return Period(length=length, units=units)
|
|
49
|
+
elif units == TimeUnits.Months:
|
|
50
|
+
if length % 12 == 0:
|
|
51
|
+
length //= 12
|
|
52
|
+
units = TimeUnits.Years
|
|
53
|
+
return Period(length=length, units=units)
|
|
54
|
+
elif units == TimeUnits.Days:
|
|
55
|
+
if length % 7 == 0:
|
|
56
|
+
length //= 7
|
|
57
|
+
units = TimeUnits.Weeks
|
|
58
|
+
return Period(length=length, units=units)
|
|
59
|
+
else:
|
|
60
|
+
ensureRaise(TypeError, "wrong period units type {0}".format(units))
|
|
61
|
+
|
|
62
|
+
def __div__(self, n):
|
|
63
|
+
|
|
64
|
+
resunits = self.units()
|
|
65
|
+
reslength = self.length()
|
|
66
|
+
|
|
67
|
+
if reslength % n == 0:
|
|
68
|
+
reslength /= n
|
|
69
|
+
else:
|
|
70
|
+
if resunits == TimeUnits.Years:
|
|
71
|
+
reslength *= 12
|
|
72
|
+
resunits = TimeUnits.Months
|
|
73
|
+
elif resunits == TimeUnits.Weeks:
|
|
74
|
+
reslength *= 7
|
|
75
|
+
resunits = TimeUnits.Days
|
|
76
|
+
|
|
77
|
+
require(reslength % n == 0, ValueError, "{0} cannot be divided by {1:d}".format(self, n))
|
|
78
|
+
|
|
79
|
+
reslength //= n
|
|
80
|
+
|
|
81
|
+
return Period(length=reslength, units=resunits)
|
|
82
|
+
|
|
83
|
+
# only work for python 3
|
|
84
|
+
def __truediv__(self, n):
|
|
85
|
+
|
|
86
|
+
resunits = self.units()
|
|
87
|
+
reslength = self.length()
|
|
88
|
+
|
|
89
|
+
if reslength % n == 0:
|
|
90
|
+
reslength /= n
|
|
91
|
+
else:
|
|
92
|
+
if resunits == TimeUnits.Years:
|
|
93
|
+
reslength *= 12
|
|
94
|
+
resunits = TimeUnits.Months
|
|
95
|
+
elif resunits == TimeUnits.Weeks:
|
|
96
|
+
reslength *= 7
|
|
97
|
+
resunits = TimeUnits.Days
|
|
98
|
+
|
|
99
|
+
require(reslength % n == 0, ValueError, "{0} cannot be divided by {1:d}".format(self, n))
|
|
100
|
+
reslength //= n
|
|
101
|
+
|
|
102
|
+
return Period(length=reslength, units=resunits)
|
|
103
|
+
|
|
104
|
+
def __add__(self, p2):
|
|
105
|
+
|
|
106
|
+
reslength = self.length()
|
|
107
|
+
resunits = self.units()
|
|
108
|
+
p2length = p2.length()
|
|
109
|
+
p2units = p2.units()
|
|
110
|
+
|
|
111
|
+
if reslength == 0:
|
|
112
|
+
return Period(length=p2length, units=p2units)
|
|
113
|
+
elif resunits == p2units:
|
|
114
|
+
reslength += p2length
|
|
115
|
+
return Period(length=reslength, units=resunits)
|
|
116
|
+
else:
|
|
117
|
+
if resunits == TimeUnits.Years:
|
|
118
|
+
if p2units == TimeUnits.Months:
|
|
119
|
+
resunits = TimeUnits.Months
|
|
120
|
+
reslength = reslength * 12 + p2length
|
|
121
|
+
elif p2units == TimeUnits.Weeks or p2units == TimeUnits.Days or p2units == TimeUnits.BDays:
|
|
122
|
+
require(p2length == 0, ValueError, "impossible addition between {0} and {1}".format(self, p2))
|
|
123
|
+
else:
|
|
124
|
+
raise ValueError("unknown time unit ({0:d})".format(p2units))
|
|
125
|
+
return Period(length=reslength, units=resunits)
|
|
126
|
+
elif resunits == TimeUnits.Months:
|
|
127
|
+
if p2units == TimeUnits.Years:
|
|
128
|
+
reslength += 12 * p2length
|
|
129
|
+
elif p2units == TimeUnits.Weeks or p2units == TimeUnits.Days or p2units == TimeUnits.BDays:
|
|
130
|
+
require(p2length == 0, ValueError, "impossible addition between {0} and {1}".format(self, p2))
|
|
131
|
+
else:
|
|
132
|
+
raise ValueError("unknown time unit ({0:d})".format(p2units))
|
|
133
|
+
return Period(length=reslength, units=resunits)
|
|
134
|
+
elif resunits == TimeUnits.Weeks:
|
|
135
|
+
if p2units == TimeUnits.Days:
|
|
136
|
+
resunits = TimeUnits.Days
|
|
137
|
+
reslength = reslength * 7 + p2length
|
|
138
|
+
elif p2units == TimeUnits.Years or p2units == TimeUnits.Months or p2units == TimeUnits.BDays:
|
|
139
|
+
require(p2length == 0, ValueError, "impossible addition between {0} and {1}".format(self, p2))
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError("unknown time unit ({0:d})".format(p2units))
|
|
142
|
+
return Period(length=reslength, units=resunits)
|
|
143
|
+
elif resunits == TimeUnits.Days:
|
|
144
|
+
if p2units == TimeUnits.Weeks:
|
|
145
|
+
reslength += 7 * p2length
|
|
146
|
+
elif p2units == TimeUnits.Years or p2units == TimeUnits.Months or p2units == TimeUnits.BDays:
|
|
147
|
+
require(p2length == 0, ValueError, "impossible addition between {0} and {1}".format(self, p2))
|
|
148
|
+
else:
|
|
149
|
+
raise ValueError("unknown time unit ({0:d})".format(p2units))
|
|
150
|
+
return Period(length=reslength, units=resunits)
|
|
151
|
+
elif resunits == TimeUnits.BDays:
|
|
152
|
+
if p2units == TimeUnits.Years or p2units == TimeUnits.Months or p2units == TimeUnits.Weeks or p2units == TimeUnits.Days:
|
|
153
|
+
require(p2length == 0, ValueError, "impossible addition between {0} and {1}".format(self, p2))
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError("unknown time unit ({0:d})".format(p2units))
|
|
156
|
+
return Period(length=reslength, units=resunits)
|
|
157
|
+
|
|
158
|
+
def __neg__(self):
|
|
159
|
+
return Period(length=-self.length(), units=self.units())
|
|
160
|
+
|
|
161
|
+
def __sub__(self, p2):
|
|
162
|
+
return self + (-p2)
|
|
163
|
+
|
|
164
|
+
def __str__(self):
|
|
165
|
+
out = ""
|
|
166
|
+
n = self.length()
|
|
167
|
+
m = 0
|
|
168
|
+
units = self.units()
|
|
169
|
+
|
|
170
|
+
if units == TimeUnits.Days:
|
|
171
|
+
if n >= 7:
|
|
172
|
+
m = int(floor(n / 7, ))
|
|
173
|
+
out = '{0}W'.format(m)
|
|
174
|
+
n %= 7
|
|
175
|
+
if n != 0 or m == 0:
|
|
176
|
+
return '{0}{1}D'.format(out, n)
|
|
177
|
+
else:
|
|
178
|
+
return out
|
|
179
|
+
elif units == TimeUnits.Weeks:
|
|
180
|
+
return '{0}W'.format(n)
|
|
181
|
+
elif units == TimeUnits.Months:
|
|
182
|
+
if n >= 12:
|
|
183
|
+
m = int(floor(n / 12.))
|
|
184
|
+
out = '{0}Y'.format(m)
|
|
185
|
+
n %= 12
|
|
186
|
+
if n != 0 or m == 0:
|
|
187
|
+
return '{0}{1}M'.format(out, n)
|
|
188
|
+
else:
|
|
189
|
+
return out
|
|
190
|
+
elif units == TimeUnits.Years:
|
|
191
|
+
return '{0}Y'.format(n)
|
|
192
|
+
elif units == TimeUnits.BDays:
|
|
193
|
+
return '{0}B'.format(n)
|
|
194
|
+
|
|
195
|
+
def __deepcopy__(self, memo):
|
|
196
|
+
return Period(length=self.length(), units=self.units())
|
|
197
|
+
|
|
198
|
+
def __reduce__(self):
|
|
199
|
+
d = {}
|
|
200
|
+
|
|
201
|
+
return Period, (None, self.length(), self.units()), d
|
|
202
|
+
|
|
203
|
+
def __setstate__(self, state):
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
def __lt__(self, right):
|
|
207
|
+
return _lt_cmp(self, right)
|
|
208
|
+
|
|
209
|
+
def __eq__(self, right):
|
|
210
|
+
return not (_lt_cmp(self, right) or _lt_cmp(right, self))
|
|
211
|
+
|
|
212
|
+
def __ne__(self, right):
|
|
213
|
+
return _lt_cmp(self, right) or _lt_cmp(right, self)
|
|
214
|
+
|
|
215
|
+
def __gt__(self, right):
|
|
216
|
+
return _lt_cmp(right, self)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def check_period(p):
|
|
220
|
+
if isinstance(p, Period):
|
|
221
|
+
return p
|
|
222
|
+
else:
|
|
223
|
+
return Period(p)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# implementation detail
|
|
227
|
+
|
|
228
|
+
def _lt_cmp(p1, p2):
|
|
229
|
+
p1lim: tuple
|
|
230
|
+
p2lim: tuple
|
|
231
|
+
p1length = p1.length()
|
|
232
|
+
p1units = p1.units()
|
|
233
|
+
p2length = p2.length()
|
|
234
|
+
p2units = p2.units()
|
|
235
|
+
|
|
236
|
+
if p1length == 0:
|
|
237
|
+
return p2length > 0
|
|
238
|
+
|
|
239
|
+
if p2length == 0:
|
|
240
|
+
return p1length < 0
|
|
241
|
+
|
|
242
|
+
# exact comparisons
|
|
243
|
+
if p1units == p2units:
|
|
244
|
+
return p1length < p2length
|
|
245
|
+
elif p1units == TimeUnits.Months and p2units == TimeUnits.Years:
|
|
246
|
+
return p1length < (p2length * 12)
|
|
247
|
+
elif p1units == TimeUnits.Years and p2units == TimeUnits.Months:
|
|
248
|
+
return (p1length * 12) < p2length
|
|
249
|
+
elif p1units == TimeUnits.Days and p2units == TimeUnits.Weeks:
|
|
250
|
+
return p1length < (p2length * 7)
|
|
251
|
+
elif p1units == TimeUnits.Weeks and p2units == TimeUnits.Days:
|
|
252
|
+
return (p1length * 7) < p2length
|
|
253
|
+
|
|
254
|
+
# inexact comparisons (handled by converting to days and using limits)
|
|
255
|
+
|
|
256
|
+
p1lim = _daysMinMax(p1)
|
|
257
|
+
p2lim = _daysMinMax(p2)
|
|
258
|
+
|
|
259
|
+
if p1lim[1] < p2lim[0]:
|
|
260
|
+
return True
|
|
261
|
+
elif p1lim[0] >= p2lim[1]:
|
|
262
|
+
return False
|
|
263
|
+
else:
|
|
264
|
+
ensureRaise(ValueError, "undecidable comparison between {0} and {1}".format(p1, p2))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _daysMinMax(p):
|
|
268
|
+
units = p.units()
|
|
269
|
+
length = p.length()
|
|
270
|
+
|
|
271
|
+
if units == TimeUnits.Days:
|
|
272
|
+
return length, length
|
|
273
|
+
elif units == TimeUnits.Weeks:
|
|
274
|
+
return 7 * length, 7 * length
|
|
275
|
+
elif units == TimeUnits.Months:
|
|
276
|
+
return 28 * length, 31 * length
|
|
277
|
+
elif units == TimeUnits.Years:
|
|
278
|
+
return 365 * length, 366 * length
|
|
279
|
+
elif units == TimeUnits.BDays:
|
|
280
|
+
ensureRaise(ValueError, "Business days unit has not min max days")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _lt_cmp(p1, p2):
|
|
284
|
+
p1lim: tuple
|
|
285
|
+
p2lim: tuple
|
|
286
|
+
p1length = p1.length()
|
|
287
|
+
p1units = p1.units()
|
|
288
|
+
p2length = p2.length()
|
|
289
|
+
p2units = p2.units()
|
|
290
|
+
|
|
291
|
+
if p1length == 0:
|
|
292
|
+
return p2length > 0
|
|
293
|
+
|
|
294
|
+
if p2length == 0:
|
|
295
|
+
return p1length < 0
|
|
296
|
+
|
|
297
|
+
# exact comparisons
|
|
298
|
+
if p1units == p2units:
|
|
299
|
+
return p1length < p2length
|
|
300
|
+
elif p1units == TimeUnits.Months and p2units == TimeUnits.Years:
|
|
301
|
+
return p1length < (p2length * 12)
|
|
302
|
+
elif p1units == TimeUnits.Years and p2units == TimeUnits.Months:
|
|
303
|
+
return (p1length * 12) < p2length
|
|
304
|
+
elif p1units == TimeUnits.Days and p2units == TimeUnits.Weeks:
|
|
305
|
+
return p1length < (p2length * 7)
|
|
306
|
+
elif p1units == TimeUnits.Weeks and p2units == TimeUnits.Days:
|
|
307
|
+
return (p1length * 7) < p2length
|
|
308
|
+
|
|
309
|
+
# inexact comparisons (handled by converting to days and using limits)
|
|
310
|
+
|
|
311
|
+
p1lim = _daysMinMax(p1)
|
|
312
|
+
p2lim = _daysMinMax(p2)
|
|
313
|
+
|
|
314
|
+
if p1lim[1] < p2lim[0]:
|
|
315
|
+
return True
|
|
316
|
+
elif p1lim[0] >= p2lim[1]:
|
|
317
|
+
return False
|
|
318
|
+
else:
|
|
319
|
+
ensureRaise(ValueError, "undecidable comparison between {0} and {1}".format(p1, p2))
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from src.rlquantdata.DateUtilities.Date_enum import BizDayConventions, DateGeneration, TimeUnits
|
|
4
|
+
from src.rlquantdata.DateUtilities.Date import Date
|
|
5
|
+
from src.rlquantdata.DateUtilities.Period import Period
|
|
6
|
+
from src.rlquantdata.DateUtilities.Calendar import Calendar
|
|
7
|
+
from src.rlquantdata.DateUtilities.Settings import Settings
|
|
8
|
+
from src.rlquantdata.DateUtilities.Asserts import require
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Schedule(object):
|
|
12
|
+
|
|
13
|
+
def __init__(self,
|
|
14
|
+
effectiveDate,
|
|
15
|
+
terminationDate,
|
|
16
|
+
tenor,
|
|
17
|
+
calendar,
|
|
18
|
+
convention=BizDayConventions.Following,
|
|
19
|
+
terminationConvention=BizDayConventions.Following,
|
|
20
|
+
dateGenerationRule=DateGeneration.Forward,
|
|
21
|
+
endOfMonth=False,
|
|
22
|
+
firstDate=None,
|
|
23
|
+
nextToLastDate=None):
|
|
24
|
+
|
|
25
|
+
i: int
|
|
26
|
+
dateLen: int
|
|
27
|
+
nullCalendar: Calendar
|
|
28
|
+
evalDate: Date
|
|
29
|
+
y: int
|
|
30
|
+
periods: int
|
|
31
|
+
seed: Date
|
|
32
|
+
temp: Date
|
|
33
|
+
exitDate: Date
|
|
34
|
+
|
|
35
|
+
# Initialize private data
|
|
36
|
+
self._effectiveDate = effectiveDate
|
|
37
|
+
self._terminationDate = terminationDate
|
|
38
|
+
self._tenor = tenor
|
|
39
|
+
self._cal = calendar
|
|
40
|
+
self._convention = convention
|
|
41
|
+
self._terminationConvention = terminationConvention
|
|
42
|
+
self._rule = dateGenerationRule
|
|
43
|
+
self._dates = []
|
|
44
|
+
self._isRegular = []
|
|
45
|
+
|
|
46
|
+
if tenor < Period("1M"):
|
|
47
|
+
self._endOfMonth = False
|
|
48
|
+
else:
|
|
49
|
+
self._endOfMonth = endOfMonth
|
|
50
|
+
|
|
51
|
+
if not firstDate or firstDate == effectiveDate:
|
|
52
|
+
self._firstDate = None
|
|
53
|
+
else:
|
|
54
|
+
self._firstDate = firstDate
|
|
55
|
+
|
|
56
|
+
if not nextToLastDate or nextToLastDate == terminationDate:
|
|
57
|
+
self._nextToLastDate = None
|
|
58
|
+
else:
|
|
59
|
+
self._nextToLastDate = nextToLastDate
|
|
60
|
+
|
|
61
|
+
# in many cases (e.g. non-expired bonds) the effective date is not
|
|
62
|
+
# really necessary. In these cases a decent placeholder is enough
|
|
63
|
+
if not effectiveDate and not firstDate and dateGenerationRule == DateGeneration.Backward:
|
|
64
|
+
evalDate = Settings.evaluationDate
|
|
65
|
+
require(evalDate < terminationDate, ValueError, "null effective date")
|
|
66
|
+
if nextToLastDate:
|
|
67
|
+
y = int((nextToLastDate - evalDate) / 366) + 1
|
|
68
|
+
effectiveDate = nextToLastDate - Period(length=y, units=TimeUnits.Years)
|
|
69
|
+
else:
|
|
70
|
+
y = int((terminationDate - evalDate) / 366) + 1
|
|
71
|
+
effectiveDate = terminationDate - Period(length=y, units=TimeUnits.Years)
|
|
72
|
+
else:
|
|
73
|
+
require(effectiveDate, ValueError, "null effective date")
|
|
74
|
+
|
|
75
|
+
require(effectiveDate < terminationDate, ValueError, "effective date ({0}) "
|
|
76
|
+
"later than or equal to termination date ({1}"
|
|
77
|
+
.format(effectiveDate, terminationDate))
|
|
78
|
+
|
|
79
|
+
if tenor.length() == 0:
|
|
80
|
+
self._rule = DateGeneration.Zero
|
|
81
|
+
else:
|
|
82
|
+
require(tenor.length() > 0, ValueError, "non positive tenor ({0:d}) not allowed".format(tenor.length()))
|
|
83
|
+
|
|
84
|
+
if self._firstDate:
|
|
85
|
+
if self._rule == DateGeneration.Backward or self._rule == DateGeneration.Forward:
|
|
86
|
+
require(effectiveDate < self._firstDate < terminationDate, ValueError,
|
|
87
|
+
"first date ({0}) out of effective-termination date range [{1}, {2})"
|
|
88
|
+
.format(self._firstDate, effectiveDate, terminationDate))
|
|
89
|
+
# we should ensure that the above condition is still
|
|
90
|
+
# verified after adjustment
|
|
91
|
+
elif self._rule == DateGeneration.Zero:
|
|
92
|
+
raise ValueError("first date incompatible with {0:d} date generation rule".format(self._rule))
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError("unknown rule ({0:d})".format(self._rule))
|
|
95
|
+
|
|
96
|
+
if self._nextToLastDate:
|
|
97
|
+
if self._rule == DateGeneration.Backward or self._rule == DateGeneration.Forward:
|
|
98
|
+
require(effectiveDate < self._nextToLastDate < terminationDate, ValueError,
|
|
99
|
+
"next to last date ({0}) out of effective-termination date range [{1}, {2})"
|
|
100
|
+
.format(self._nextToLastDate, effectiveDate, terminationDate))
|
|
101
|
+
# we should ensure that the above condition is still
|
|
102
|
+
# verified after adjustment
|
|
103
|
+
elif self._rule == DateGeneration.Zero:
|
|
104
|
+
raise ValueError("next to last date incompatible with {0:d} date generation rule".format(self._rule))
|
|
105
|
+
else:
|
|
106
|
+
raise ValueError("unknown rule ({0:d})".format(self._rule))
|
|
107
|
+
|
|
108
|
+
# calendar needed for endOfMonth adjustment
|
|
109
|
+
nullCalendar = Calendar("Null")
|
|
110
|
+
periods = 1
|
|
111
|
+
|
|
112
|
+
if self._rule == DateGeneration.Zero:
|
|
113
|
+
self._tenor = Period(length=0, units=TimeUnits.Years)
|
|
114
|
+
self._dates.extend([effectiveDate, terminationDate])
|
|
115
|
+
self._isRegular.append(True)
|
|
116
|
+
elif self._rule == DateGeneration.Backward:
|
|
117
|
+
self._dates.append(terminationDate)
|
|
118
|
+
seed = terminationDate
|
|
119
|
+
if self._nextToLastDate:
|
|
120
|
+
self._dates.insert(0, self._nextToLastDate)
|
|
121
|
+
temp = nullCalendar.advanceDate(seed,
|
|
122
|
+
Period(length=-periods * self._tenor.length(),
|
|
123
|
+
units=self._tenor.units()),
|
|
124
|
+
convention, self._endOfMonth)
|
|
125
|
+
if temp != self._nextToLastDate:
|
|
126
|
+
self._isRegular.insert(0, False)
|
|
127
|
+
else:
|
|
128
|
+
self._isRegular.insert(0, True)
|
|
129
|
+
seed = self._nextToLastDate
|
|
130
|
+
|
|
131
|
+
exitDate = effectiveDate
|
|
132
|
+
if self._firstDate:
|
|
133
|
+
exitDate = self._firstDate
|
|
134
|
+
|
|
135
|
+
while True:
|
|
136
|
+
temp = nullCalendar.advanceDate(seed,
|
|
137
|
+
Period(length=-periods * self._tenor.length(),
|
|
138
|
+
units=self._tenor.units()),
|
|
139
|
+
convention, self._endOfMonth)
|
|
140
|
+
if temp < exitDate:
|
|
141
|
+
if self._firstDate and self._cal.adjustDate(self._dates[0], convention) != self._cal.adjustDate(
|
|
142
|
+
self._firstDate, convention):
|
|
143
|
+
self._dates.insert(0, self._firstDate)
|
|
144
|
+
self._isRegular.insert(0, False)
|
|
145
|
+
break
|
|
146
|
+
else:
|
|
147
|
+
# skip dates that would result in duplicates
|
|
148
|
+
# after adjustment
|
|
149
|
+
if self._cal.adjustDate(self._dates[0], convention) != self._cal.adjustDate(temp, convention):
|
|
150
|
+
self._dates.insert(0, temp)
|
|
151
|
+
self._isRegular.insert(0, True)
|
|
152
|
+
periods += 1
|
|
153
|
+
|
|
154
|
+
if self._cal.adjustDate(self._dates[0], convention) != self._cal.adjustDate(effectiveDate, convention):
|
|
155
|
+
self._dates.insert(0, effectiveDate)
|
|
156
|
+
self._isRegular.insert(0, False)
|
|
157
|
+
|
|
158
|
+
elif self._rule == DateGeneration.Forward:
|
|
159
|
+
self._dates.append(effectiveDate)
|
|
160
|
+
|
|
161
|
+
seed = self._dates[-1]
|
|
162
|
+
|
|
163
|
+
if self._firstDate:
|
|
164
|
+
self._dates.append(self._firstDate)
|
|
165
|
+
temp = nullCalendar.advanceDate(seed,
|
|
166
|
+
Period(length=periods * self._tenor.length(),
|
|
167
|
+
units=self._tenor.units()),
|
|
168
|
+
convention, self._endOfMonth)
|
|
169
|
+
if temp != self._firstDate:
|
|
170
|
+
self._isRegular.append(False)
|
|
171
|
+
else:
|
|
172
|
+
self._isRegular.append(True)
|
|
173
|
+
seed = self._firstDate
|
|
174
|
+
|
|
175
|
+
exitDate = terminationDate
|
|
176
|
+
if self._nextToLastDate:
|
|
177
|
+
exitDate = self._nextToLastDate
|
|
178
|
+
|
|
179
|
+
while True:
|
|
180
|
+
temp = nullCalendar.advanceDate(seed,
|
|
181
|
+
Period(length=periods * self._tenor.length(),
|
|
182
|
+
units=self._tenor.units()),
|
|
183
|
+
convention, self._endOfMonth)
|
|
184
|
+
if temp > exitDate:
|
|
185
|
+
if self._nextToLastDate and self._cal.adjustDate(self._dates[-1],
|
|
186
|
+
convention) != self._cal.adjustDate(
|
|
187
|
+
self._nextToLastDate, convention):
|
|
188
|
+
self._dates.append(self._nextToLastDate)
|
|
189
|
+
self._isRegular.append(False)
|
|
190
|
+
break
|
|
191
|
+
else:
|
|
192
|
+
# skip dates that would result in duplicates
|
|
193
|
+
# after adjustment
|
|
194
|
+
if self._cal.adjustDate(self._dates[-1], convention) != self._cal.adjustDate(temp, convention):
|
|
195
|
+
self._dates.append(temp)
|
|
196
|
+
self._isRegular.append(True)
|
|
197
|
+
periods += 1
|
|
198
|
+
|
|
199
|
+
if self._cal.adjustDate(self._dates[-1], terminationConvention) != self._cal.adjustDate(terminationDate,
|
|
200
|
+
terminationConvention):
|
|
201
|
+
self._dates.append(terminationDate)
|
|
202
|
+
self._isRegular.append(False)
|
|
203
|
+
else:
|
|
204
|
+
raise ValueError("unknown rule ({0:d})".format(self._rule))
|
|
205
|
+
|
|
206
|
+
# adjustments
|
|
207
|
+
if self._endOfMonth and self._cal.isEndOfMonth(seed):
|
|
208
|
+
# adjust to end of month
|
|
209
|
+
if convention == BizDayConventions.Unadjusted:
|
|
210
|
+
for i in range(len(self._dates) - 1):
|
|
211
|
+
self._dates[i] = Date.endOfMonth(self._dates[i])
|
|
212
|
+
else:
|
|
213
|
+
for i in range(len(self._dates) - 1):
|
|
214
|
+
self._dates[i] = self._cal.endOfMonth(self._dates[i])
|
|
215
|
+
|
|
216
|
+
if terminationConvention != BizDayConventions.Unadjusted:
|
|
217
|
+
self._dates[0] = self._cal.endOfMonth(self._dates[0])
|
|
218
|
+
self._dates[-1] = self._cal.endOfMonth(self._dates[-1])
|
|
219
|
+
else:
|
|
220
|
+
if self._rule == DateGeneration.Backward:
|
|
221
|
+
self._dates[-1] = Date.endOfMonth(self._dates[-1])
|
|
222
|
+
else:
|
|
223
|
+
self._dates[0] = Date.endOfMonth(self._dates[0])
|
|
224
|
+
else:
|
|
225
|
+
for i in range(len(self._dates) - 1):
|
|
226
|
+
self._dates[i] = self._cal.adjustDate(self._dates[i], convention)
|
|
227
|
+
|
|
228
|
+
if terminationConvention != BizDayConventions.Unadjusted:
|
|
229
|
+
self._dates[-1] = self._cal.adjustDate(self._dates[-1], terminationConvention)
|
|
230
|
+
|
|
231
|
+
# Final safety checks to remove extra next-to-last date, if
|
|
232
|
+
# necessary. It can happen to be equal or later than the end
|
|
233
|
+
# date due to EOM adjustments (see the Schedule test suite
|
|
234
|
+
# for an example).
|
|
235
|
+
|
|
236
|
+
dateLen = len(self._dates)
|
|
237
|
+
|
|
238
|
+
if dateLen >= 2 and self._dates[dateLen - 2] >= self._dates[-1]:
|
|
239
|
+
self._isRegular[dateLen - 2] = (self._dates[dateLen - 2] == self._dates[-1])
|
|
240
|
+
self._dates[dateLen - 2] = self._dates[-1]
|
|
241
|
+
self._dates.pop()
|
|
242
|
+
self._isRegular.pop()
|
|
243
|
+
|
|
244
|
+
if len(self._dates) >= 2 and self._dates[1] <= self._dates[0]:
|
|
245
|
+
self._isRegular[1] = (self._dates[1] == self._dates[0])
|
|
246
|
+
self._dates[1] = self._dates[0]
|
|
247
|
+
self._dates = self._dates[1:]
|
|
248
|
+
self._isRegular = self._isRegular[1:]
|
|
249
|
+
|
|
250
|
+
require(len(self._dates) >= 1, ValueError, "degenerate single date ({0}) schedule\n"
|
|
251
|
+
"seed date: {1}\n"
|
|
252
|
+
"exit date: {2}\n"
|
|
253
|
+
"effective date: {3}\n"
|
|
254
|
+
"first date: {4}\n"
|
|
255
|
+
"next to last date: {5}\n"
|
|
256
|
+
"termination date: {6}\n"
|
|
257
|
+
"generation rule: {7}\n"
|
|
258
|
+
"end of month: {8}\n"
|
|
259
|
+
.format(self._dates[0],
|
|
260
|
+
seed, exitDate,
|
|
261
|
+
effectiveDate,
|
|
262
|
+
firstDate,
|
|
263
|
+
nextToLastDate,
|
|
264
|
+
terminationDate,
|
|
265
|
+
self._rule, self._endOfMonth))
|
|
266
|
+
|
|
267
|
+
def size(self):
|
|
268
|
+
return len(self._dates)
|
|
269
|
+
|
|
270
|
+
def calendar(self):
|
|
271
|
+
return self._cal
|
|
272
|
+
|
|
273
|
+
def tenor(self):
|
|
274
|
+
return self._tenor
|
|
275
|
+
|
|
276
|
+
def endOfMonth(self):
|
|
277
|
+
return self._endOfMonth
|
|
278
|
+
|
|
279
|
+
def isRegular(self, i):
|
|
280
|
+
return self._isRegular[i - 1]
|
|
281
|
+
|
|
282
|
+
def __getitem__(self, item):
|
|
283
|
+
return self._dates[item]
|
|
284
|
+
|
|
285
|
+
def __deepcopy__(self, memo):
|
|
286
|
+
return Schedule(self._effectiveDate,
|
|
287
|
+
self._terminationDate,
|
|
288
|
+
self._tenor,
|
|
289
|
+
self._cal,
|
|
290
|
+
self._convention,
|
|
291
|
+
self._terminationConvention,
|
|
292
|
+
self._rule,
|
|
293
|
+
self._endOfMonth,
|
|
294
|
+
self._firstDate,
|
|
295
|
+
self._nextToLastDate)
|
|
296
|
+
|
|
297
|
+
def __reduce__(self):
|
|
298
|
+
d = {}
|
|
299
|
+
|
|
300
|
+
return Schedule, (self._effectiveDate,
|
|
301
|
+
self._terminationDate,
|
|
302
|
+
self._tenor,
|
|
303
|
+
self._cal,
|
|
304
|
+
self._convention,
|
|
305
|
+
self._terminationConvention,
|
|
306
|
+
self._rule,
|
|
307
|
+
self._endOfMonth,
|
|
308
|
+
self._firstDate,
|
|
309
|
+
self._nextToLastDate), d
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from src.rlquantdata.DateUtilities import Date
|
|
4
|
+
from src.rlquantdata.DateUtilities import require
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SettingsFactory(object):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._evaluationDate = None
|
|
10
|
+
self._includeToday = None
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def evaluationDate(self):
|
|
14
|
+
if not self._evaluationDate:
|
|
15
|
+
return Date.todaysDate()
|
|
16
|
+
return self._evaluationDate
|
|
17
|
+
|
|
18
|
+
@evaluationDate.setter
|
|
19
|
+
def evaluationDate(self, value):
|
|
20
|
+
require(isinstance(value, Date), ValueError, "{0} is not a valid PyFin date object".format(value))
|
|
21
|
+
self._evaluationDate = value
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def includeTodaysCashFlows(self):
|
|
25
|
+
return self._includeToday
|
|
26
|
+
|
|
27
|
+
@includeTodaysCashFlows.setter
|
|
28
|
+
def includeTodaysCashFlows(self, value):
|
|
29
|
+
self._evaluationDate = value
|
|
30
|
+
|
|
31
|
+
def resetEvaluationDate(self):
|
|
32
|
+
self._evaluationDate = None
|
|
33
|
+
|
|
34
|
+
def anchorEvaluationDate(self):
|
|
35
|
+
if self._evaluationDate is None:
|
|
36
|
+
self._evaluationDate = Date.todaysDate()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
Settings = SettingsFactory()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
u"""
|
|
3
|
+
Created on 2015-7-9
|
|
4
|
+
|
|
5
|
+
@author: cheng.li
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from src.rlquantdata.DateUtilities.Asserts import require
|
|
9
|
+
from src.rlquantdata.DateUtilities.Date import Date
|
|
10
|
+
from src.rlquantdata.DateUtilities.Date import check_date
|
|
11
|
+
from src.rlquantdata.DateUtilities.Period import Period
|
|
12
|
+
from src.rlquantdata.DateUtilities.Period import check_period
|
|
13
|
+
from src.rlquantdata.DateUtilities.Schedule import Schedule
|
|
14
|
+
from src import rlquantdata
|
|
15
|
+
import src.rlquantdata.DateUtilities.Date_enum
|
|
16
|
+
|
|
17
|
+
__all__ = ['require',
|
|
18
|
+
'Date',
|
|
19
|
+
'Period',
|
|
20
|
+
'Schedule',
|
|
21
|
+
'check_date',
|
|
22
|
+
'check_period',
|
|
23
|
+
'DateUtilities',
|
|
24
|
+
'Date_enum']
|
|
File without changes
|