Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
conventionsbasedfutureexpiry.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
22
23using namespace QuantLib;
24using std::string;
25
26namespace ore {
27namespace data {
28
29ConventionsBasedFutureExpiry::ConventionsBasedFutureExpiry(const std::string& commName, Size maxIterations)
30 : maxIterations_(maxIterations) {
31 auto p = QuantLib::ext::dynamic_pointer_cast<CommodityFutureConvention>(
32 InstrumentConventions::instance().conventions()->get(commName));
33 QL_REQUIRE(p, "ConventionsBasedFutureExpiry: could not cast to CommodityFutureConvention for '"
34 << commName << "', this is an internal error. Contact support.");
35 convention_ = *p;
36}
37
38ConventionsBasedFutureExpiry::ConventionsBasedFutureExpiry(const CommodityFutureConvention& convention,
39 QuantLib::Size maxIterations)
40 : convention_(convention), maxIterations_(maxIterations) {}
41
42Date ConventionsBasedFutureExpiry::nextExpiry(bool includeExpiry, const Date& referenceDate, Natural offset,
43 bool forOption) {
44
45 // Set the date relative to which we are calculating the next expiry
46 Date today = referenceDate == Date() ? Settings::instance().evaluationDate() : referenceDate;
47
48 // Get the next expiry date relative to referenceDate
49 Date expiryDate = nextExpiry(today, forOption);
50
51 // If expiry date equals today and we have asked not to include expiry, return next contract's expiry
52 if (expiryDate == today && !includeExpiry && offset == 0) {
53 expiryDate = nextExpiry(expiryDate + 1 * Days, forOption);
54 }
55
56 // If offset is greater than 0, keep getting next expiry out
57 while (offset > 0) {
58 expiryDate = nextExpiry(expiryDate + 1 * Days, forOption);
59 offset--;
60 }
61
62 return expiryDate;
63}
64
65Date ConventionsBasedFutureExpiry::priorExpiry(bool includeExpiry, const Date& referenceDate, bool forOption) {
66
67 // Set the date relative to which we are calculating the preceding expiry
68 Date today = referenceDate == Date() ? Settings::instance().evaluationDate() : referenceDate;
69
70 // Get the next expiry relative to the reference date (including the reference date)
71 Date expiry = nextExpiry(true, today, 0, forOption);
72
73 // If that expiry is equal to reference date and we have set includeExpiry to true, we are done.
74 if (includeExpiry && expiry == today)
75 return expiry;
76
77 // Get the preceding expiry.
79 Date baseDate = convention_.expiryCalendar().advance(expiry, -p);
80 expiry = nextExpiry(true, baseDate, 0, forOption);
81
82 // May still not have the preceding expiry but must be close
83 Size counter = maxIterations_;
84 while (expiry >= today && counter > 0) {
85 baseDate--;
86 counter--;
87 expiry = nextExpiry(true, baseDate, 0, forOption);
88 }
89 QL_REQUIRE(expiry < today, "Expected that expiry " << io::iso_date(expiry) << " would be less than reference date "
90 << io::iso_date(today) << ".");
91
92 return expiry;
93}
94
95Date ConventionsBasedFutureExpiry::expiryDate(const Date& contractDate, Natural monthOffset, bool forOption) {
96 if (convention_.contractFrequency() == Daily) {
97 return nextExpiry(contractDate, forOption);
98 } else {
99 return expiry(contractDate.dayOfMonth(), contractDate.month(), contractDate.year(), monthOffset, forOption);
100 }
101}
102
103QuantLib::Date ConventionsBasedFutureExpiry::contractDate(const QuantLib::Date& expiryDate) {
104
105 if (convention_.contractFrequency() == Monthly) {
106
107 // do not attempt to invert the logic in expiry(), instead just search for a valid contract month
108 // in a reasonable range
109
110 for (Size m = 0; m < 120; ++m) {
111 try {
112 Date tmp = Date(15, expiryDate.month(), expiryDate.year()) + m * Months;
113 if (expiry(tmp.dayOfMonth(), tmp.month(), tmp.year(), 0, false) == expiryDate)
114 return tmp;
115 } catch (...) {
116 }
117 try {
118 Date tmp = Date(15, expiryDate.month(), expiryDate.year()) - m * Months;
119 if (expiry(tmp.dayOfMonth(), tmp.month(), tmp.year(), 0, false) == expiryDate)
120 return tmp;
121 } catch (...) {
122 }
123 }
124
125 } else {
126
127 // daily of weekly contract frequency => we can use contract date = expiry date
128
129 return expiryDate;
130 }
131
132 QL_FAIL("ConventionsBasedFutureExpiry::contractDate(" << expiryDate << "): could not imply contract date. This is an internal error. Contact support.");
133}
134
135QuantLib::Date ConventionsBasedFutureExpiry::applyFutureMonthOffset(const QuantLib::Date& contractDate,
136 Natural futureMonthOffset) {
137
138 Date tmp = contractDate;
139
140 if (convention_.contractFrequency() == Monthly) {
141 tmp = Date(15, contractDate.month(), contractDate.year()) + futureMonthOffset * Months;
142 }
143
144 return tmp;
145}
146
147Date ConventionsBasedFutureExpiry::expiry(Day dayOfMonth, Month contractMonth, Year contractYear, QuantLib::Natural monthOffset,
148 bool forOption) const {
149
150 Date expiry;
151 if (convention_.contractFrequency() == Weekly) {
153 "Please change anchorType to WeeklyDayOfTheWeek for weekly contract expiries");
154 Date d(dayOfMonth, contractMonth, contractYear);
155 expiry = convention_.expiryCalendar().adjust(d - d.weekday() + convention_.weekday(),
157 } else {
158 // Apply month offset if non-zero
159 if (monthOffset > 0) {
160 Date newDate = Date(15, contractMonth, contractYear) + monthOffset * Months;
161 contractMonth = newDate.month();
162 contractYear = newDate.year();
163 }
164
165 // Move n months before (+ve) or after (-ve) for the expiry if necessary
166 if (convention_.expiryMonthLag() != 0) {
167 Date newDate = Date(15, contractMonth, contractYear) - convention_.expiryMonthLag() * Months;
168 contractMonth = newDate.month();
169 contractYear = newDate.year();
170 }
171
172 if (convention_.contractFrequency() == Monthly && !convention_.validContractMonths().empty() &&
173 convention_.validContractMonths().size() < 12 &&
174 convention_.validContractMonths().count(contractMonth) == 0) {
175 // contractMonth is in the not in the list of valid contract months
176 return Date();
177 }
178 // Calculate the relevant date in the expiry month and year
180 Date last = Date::endOfMonth(Date(1, contractMonth, contractYear));
181 if (convention_.dayOfMonth() > static_cast<Natural>(last.dayOfMonth())) {
182 expiry = last;
183 } else {
184 expiry = Date(convention_.dayOfMonth(), contractMonth, contractYear);
185 }
187 expiry = Date::nthWeekday(convention_.nth(), convention_.weekday(), contractMonth, contractYear);
189 expiry = Date(1, contractMonth, contractYear) - convention_.calendarDaysBefore() * Days;
191 if (convention_.businessDaysAfter() > 0) {
192 expiry = convention_.expiryCalendar().advance(Date(1, contractMonth, contractYear) - 1 * Days,
194 } else {
195 expiry = convention_.expiryCalendar().advance(Date(1, contractMonth, contractYear),
197 }
199 expiry = QuantExt::DateUtilities::lastWeekday(convention_.weekday(), contractMonth, contractYear);
200 } else {
201 QL_FAIL("Did not recognise the commodity future convention's anchor type");
202 }
203 // If expiry date is not a good business day, adjust it before applying the offset.
206 }
207
208 // Apply offset adjustments if necessary. A negative integer indicates that we move forward that number of days.
210 }
211
212 // If we want the option contract expiry, do the extra work here.
213 if (forOption && convention_.optionContractFrequency() == Weekly) {
215 "Please change anchorType to WeeklyDayOfTheWeek for weekly contract expiries");
216 Date d(expiry.dayOfMonth(), expiry.month(), expiry.year());
217 expiry = convention_.expiryCalendar().adjust(d - d.weekday() + convention_.optionWeekday(),
220 } else if (forOption) {
222 auto optionMonth = expiry.month();
223 auto optionYear = expiry.year();
224
226 Date newDate = Date(15, optionMonth, optionYear) - convention_.optionExpiryMonthLag() * Months;
227 optionMonth = newDate.month();
228 optionYear = newDate.year();
229 }
230
231 auto d = convention_.optionExpiryDay();
232 Date last = Date::endOfMonth(Date(1, optionMonth, optionYear));
233 if (d > static_cast<Natural>(last.dayOfMonth())) {
234 expiry = last;
235 } else {
236 expiry = Date(d, optionMonth, optionYear);
237 }
240 QL_REQUIRE(convention_.optionExpiryMonthLag() == 0 ||
242 "The expiry month lag "
243 << "and the option expiry month lag should be the same if using option expiry offset days.");
244
246 expiry, -static_cast<Integer>(convention_.optionExpiryOffset()), Days);
248 auto optionMonth = expiry.month();
249 auto optionYear = expiry.year();
250
252 Date newDate = Date(15, optionMonth, optionYear) - convention_.optionExpiryMonthLag() * Months;
253 optionMonth = newDate.month();
254 optionYear = newDate.year();
255 }
256 expiry = Date::nthWeekday(convention_.optionNth(), convention_.optionWeekday(), optionMonth, optionYear);
259 auto optionMonth = expiry.month();
260 auto optionYear = expiry.year();
261
263 Date newDate = Date(15, optionMonth, optionYear) - convention_.optionExpiryMonthLag() * Months;
264 optionMonth = newDate.month();
265 optionYear = newDate.year();
266 }
269 }
270
272
273 } else {
274 // If expiry date is one of the prohibited dates, move to preceding or following business day
275 expiry = avoidProhibited(expiry, false);
276 }
277
278 return expiry;
279}
280
281Date ConventionsBasedFutureExpiry::nextExpiry(const Date& referenceDate, bool forOption) const {
282
283 // If contract frequency is daily, next expiry is simply the next valid date on expiry calendar.
284
285 if ((convention_.contractFrequency() == Daily) && (!forOption || convention_.optionContractFrequency() == Daily)) {
286 Date expiry = convention_.expiryCalendar().adjust(referenceDate, Following);
287 return avoidProhibited(expiry, false);
288 }
289
290 // Get a contract expiry before today and increment until expiryDate is >= today
291
292 Date guideDate(15, convention_.oneContractMonth(), referenceDate.year() - 1);
293 Date expiryDate =
294 expiry(guideDate.dayOfMonth(), convention_.oneContractMonth(), referenceDate.year() - 1, 0, forOption);
295 QL_REQUIRE(expiryDate < referenceDate, "Expected the expiry date in the previous year to be before reference");
296 while (expiryDate < referenceDate) {
298 guideDate += Period(convention_.optionContractFrequency());
299 } else {
300 guideDate += Period(convention_.contractFrequency());
301 }
302 expiryDate = expiry(guideDate.dayOfMonth(), guideDate.month(), guideDate.year(), 0, forOption);
303 }
304
305 return expiryDate;
306}
307
309
311
312Date ConventionsBasedFutureExpiry::avoidProhibited(const Date& expiry, bool forOption) const {
313
314 // If expiry date is one of the prohibited dates, move to preceding or following business day.
316 Date result = expiry;
317 const auto& pes = convention_.prohibitedExpiries();
318 auto it = pes.find(PE(result));
319
320 while (it != pes.end()) {
321
322 // If not relevant, exit.
323 if ((!forOption && !it->forFuture()) || (forOption && !it->forOption()))
324 break;
325
326 auto bdc = !forOption ? it->futureBdc() : it->optionBdc();
327
328 if (bdc == Preceding || bdc == ModifiedPreceding) {
329 result = convention_.calendar().advance(result, -1, Days, bdc);
330 } else if (bdc == Following || bdc == ModifiedFollowing) {
331 result = convention_.calendar().advance(result, 1, Days, bdc);
332 } else {
333 QL_FAIL("Convention " << bdc << " associated with prohibited expiry " << io::iso_date(result)
334 << " is not supported.");
335 }
336
337 it = pes.find(PE(result));
338 }
339
340 return result;
341}
342
343} // namespace data
344} // namespace ore
Class to hold prohibited expiry information.
QuantLib::BusinessDayConvention optionBusinessDayConvention() const
QuantLib::Natural optionExpiryOffset() const
QuantLib::Weekday weekday() const
const QuantLib::Calendar & expiryCalendar() const
QuantLib::BusinessDayConvention businessDayConvention() const
QuantLib::Integer businessDaysAfter() const
QuantLib::Frequency contractFrequency() const
QuantLib::Frequency optionContractFrequency() const
OptionAnchorType optionAnchorType() const
QuantLib::Natural optionNth() const
QuantLib::Size optionExpiryMonthLag() const
const QuantLib::Calendar & calendar() const
const std::set< ProhibitedExpiry > & prohibitedExpiries() const
QuantLib::Natural optionExpiryDay() const
QuantLib::Natural dayOfMonth() const
const std::set< QuantLib::Month > & validContractMonths() const
QuantLib::Integer offsetDays() const
QuantLib::Weekday optionWeekday() const
QuantLib::Natural nth() const
QuantLib::Month oneContractMonth() const
QuantLib::Natural calendarDaysBefore() const
QuantLib::Size expiryMonthLag() const
QuantLib::Date nextExpiry(bool includeExpiry=true, const QuantLib::Date &referenceDate=QuantLib::Date(), QuantLib::Natural offset=0, bool forOption=false) override
QuantLib::Date avoidProhibited(const QuantLib::Date &expiry, bool forOption) const
Account for prohibited expiries.
QuantLib::Date expiry(QuantLib::Day dayOfMonth, QuantLib::Month contractMonth, QuantLib::Year contractYear, QuantLib::Natural monthOffset, bool forOption) const
Given a contractMonth, a contractYear and conventions, calculate the contract expiry date.
ConventionsBasedFutureExpiry(const std::string &commName, QuantLib::Size maxIterations=10)
QuantLib::Date priorExpiry(bool includeExpiry=true, const QuantLib::Date &referenceDate=QuantLib::Date(), bool forOption=false) override
QuantLib::Date applyFutureMonthOffset(const QuantLib::Date &contractDate, Natural futureMonthOffset) override
QuantLib::Date expiryDate(const QuantLib::Date &contractDate, QuantLib::Natural monthOffset=0, bool forOption=false) override
QuantLib::Size maxIterations() const
Return the maximum iterations parameter.
QuantLib::Date contractDate(const QuantLib::Date &expiryDate) override
const CommodityFutureConvention & commodityFutureConvention() const
Return the commodity future convention.
Base class for classes that perform date calculations for future contracts.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
Date referenceDate
Definition: utilities.cpp:442
Date lastWeekday(Weekday dayOfWeek, Month m, Year y)
Serializable Credit Default Swap.
Definition: namespaces.docs:23