Skip to content

Commit 8e72f76

Browse files
committed
Merge branch 'feature/QPR-13736' into 'master'
QPR-13736: added second thursday future convention Closes QPR-13736 See merge request qs/oreplus!3110
2 parents 662a3f0 + 37fe6cc commit 8e72f76

7 files changed

Lines changed: 66 additions & 31 deletions

File tree

Docs/UserGuide/parameterisation/conventions.tex

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,12 @@ \subsubsection{Future Conventions}\label{ss:conventions_future}
140140
\item Index: The underlying index of the futures, this is either a MM (i.e. Ibor) index like e.g. EUR-EURIBOR-3M or an
141141
overnight index like e.g. USD-SOFR.
142142
\item DateGenerationRule [Optional]: This should be set to `IMM' when the start and end dates of the future are
143-
following the IMM date logic or `FirstDayOfMonth' when the start and end date are the first day of a month. If not
144-
given this field defaults to `IMM'.
143+
following the IMM date logic, `FirstDayOfMonth' when the start and end date are the first day of a month, or
144+
`SecondThursday' when the expiry date is the second Thursday of the month. If not given this field defaults to `IMM'.
145145
\begin{itemize}
146-
\item For MM futures only `IMM' is allowed and the expiry date is determined as the next 3rd Wednesday of the expiry
147-
month of a future.
146+
\item For MM futures `IMM' or `SecondThursday' are allowed. The expiry date for `IMM' is determined as the next 3rd
147+
Wednesday of the expiry month of a future. The expiry date for `SecondThursday' is determined as the 2nd Thursday
148+
of the expiry month (e.g. used for AUD-BBSW-3M futures).
148149
\item For an overnight index future `IMM' means that the end date of the future is set to the 3rd Wednesday of the
149150
expiry month and the start date is set to the 3rd Wednesday of the expiry month minus the future tenor. The setting
150151
`IMM' applies to SOFR-3M futures for example. `FirstDayOfMonth' on the other hand means that the end date of the
@@ -163,8 +164,9 @@ \subsubsection{Future Conventions}\label{ss:conventions_future}
163164
are averaging the daily overnight fixings over the calculation period of the future.
164165
\end{itemize}
165166

166-
Listings \ref{lst:future_conventions_euribor_3m}, \ref{lst:future_conventions_sofr_3m},
167-
\ref{lst:future_conventions_sofr_1m} show examples for Euribor-3M, SOFR-3M and SOFR-1M future conventions.
167+
Listings \ref{lst:future_conventions_euribor_3m}, \ref{lst:future_conventions_aud_bbsw_3m},
168+
\ref{lst:future_conventions_sofr_3m}, \ref{lst:future_conventions_sofr_1m} show examples for Euribor-3M, AUD-BBSW-3M,
169+
SOFR-3M and SOFR-1M future conventions.
168170

169171
\begin{listing}[H]
170172
%\hrule\medskip
@@ -192,6 +194,19 @@ \subsubsection{Future Conventions}\label{ss:conventions_future}
192194
\label{lst:future_conventions_euribor_3m}
193195
\end{listing}
194196

197+
\begin{listing}[H]
198+
%\hrule\medskip
199+
\begin{minted}[fontsize=\footnotesize]{xml}
200+
<Future>
201+
<Id>AUD-BBSW-3M-FUTURES-CONVENTIONS</Id>
202+
<Index>AUD-BBSW-3M</Index>
203+
<DateGenerationRule>SecondThursday</DateGenerationRule>
204+
</Future>
205+
\end{minted}
206+
\caption{AUD BBSW 3M MM Future conventions (Second Thursday expiry)}
207+
\label{lst:future_conventions_aud_bbsw_3m}
208+
\end{listing}
209+
195210
\begin{listing}[H]
196211
%\hrule\medskip
197212
\begin{minted}[fontsize=\footnotesize]{xml}

OREData/ored/configuration/conventions.hpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class Conventions : public XMLSerializable {
135135

136136
/*! Get all conventions of a given type */
137137
std::set<QuantLib::ext::shared_ptr<Convention>> get(const Convention::Type& type) const;
138-
138+
139139
/*! Find a convention for an FX pair */
140140
QuantLib::ext::shared_ptr<Convention> getFxConvention(const string& ccy1, const string& ccy2) const;
141141

@@ -312,7 +312,7 @@ class DepositConvention : public Convention {
312312
*/
313313
class FutureConvention : public Convention {
314314
public:
315-
enum class DateGenerationRule { IMM, FirstDayOfMonth };
315+
enum class DateGenerationRule { IMM, FirstDayOfMonth, SecondThursday };
316316
//! \name Constructors
317317
//@{
318318
//! Default constructor
@@ -656,7 +656,7 @@ class TenorBasisSwapConvention : public Convention {
656656
//! Detailed constructor
657657
TenorBasisSwapConvention(const string& id, const string& payIndex, const string& receiveIndex,
658658
const string& receiveFrequency = "", const string& payFrequency = "",
659-
const string& spreadOnRec = "", const string& includeSpread = "",
659+
const string& spreadOnRec = "", const string& includeSpread = "",
660660
const string& subPeriodsCouponType = "");
661661
//@}
662662

@@ -1164,7 +1164,7 @@ class InflationSwapConvention : public Convention {
11641164
InflationSwapConvention(const string& id, const string& strFixCalendar, const string& strFixConvention,
11651165
const string& strDayCounter, const string& strIndex, const string& strInterpolated,
11661166
const string& strObservationLag, const string& strAdjustInfObsDates,
1167-
const string& strInfCalendar, const string& strInfConvention,
1167+
const string& strInfCalendar, const string& strInfConvention,
11681168
PublicationRoll publicationRoll = PublicationRoll::None,
11691169
const QuantLib::ext::shared_ptr<ScheduleData>& publicationScheduleData = nullptr);
11701170

@@ -1181,7 +1181,7 @@ class InflationSwapConvention : public Convention {
11811181
PublicationRoll publicationRoll() const { return publicationRoll_; }
11821182
const Schedule& publicationSchedule() const { return publicationSchedule_; }
11831183
int startDelay() const { return startDelay_; }
1184-
BusinessDayConvention startDelayConvention() const { return startDelayConvention_; }
1184+
BusinessDayConvention startDelayConvention() const { return startDelayConvention_; }
11851185

11861186
virtual void fromXML(XMLNode* node) override;
11871187
virtual XMLNode* toXML(XMLDocument& doc) const override;
@@ -1212,7 +1212,7 @@ class InflationSwapConvention : public Convention {
12121212
string strInfCalendar_;
12131213
string strInfConvention_;
12141214
string strStartDelayConvention_;
1215-
1215+
12161216
PublicationRoll publicationRoll_;
12171217
QuantLib::ext::shared_ptr<ScheduleData> publicationScheduleData_;
12181218
};
@@ -1408,7 +1408,7 @@ class CommodityFutureConvention : public Convention {
14081408
CalendarDaysBefore(const std::string& calendarDaysBefore) : calendarDaysBefore_(calendarDaysBefore) {}
14091409
std::string calendarDaysBefore_;
14101410
};
1411-
1411+
14121412
struct BusinessDaysBefore {
14131413
BusinessDaysBefore(const std::string& daysBefore) : businessDaysBefore_(daysBefore) {}
14141414
std::string businessDaysBefore_;
@@ -1470,7 +1470,7 @@ class CommodityFutureConvention : public Convention {
14701470
};
14711471
//@}
14721472

1473-
/*! Class to hold averaging information when \c isAveraging_ is \c true. It is generally needed
1473+
/*! Class to hold averaging information when \c isAveraging_ is \c true. It is generally needed
14741474
in the CommodityFutureConvention when referenced in piecewise price curve construction.
14751475
*/
14761476
class AveragingData : public XMLSerializable {
@@ -1527,7 +1527,7 @@ class CommodityFutureConvention : public Convention {
15271527
void build();
15281528
};
15291529

1530-
//! Class to store conventions for creating an off peak power index
1530+
//! Class to store conventions for creating an off peak power index
15311531
class OffPeakPowerIndexData : public XMLSerializable {
15321532
public:
15331533
//! Constructor.
@@ -1643,7 +1643,7 @@ class CommodityFutureConvention : public Convention {
16431643
QuantLib::Natural hoursPerDay = QuantLib::Null<QuantLib::Natural>(),
16441644
const QuantLib::ext::optional<OffPeakPowerIndexData>& offPeakPowerIndexData = QuantLib::ext::nullopt,
16451645
const std::string& indexName = "", const std::string& optionFrequency = "");
1646-
1646+
16471647
//! Business days before based constructor
16481648
CommodityFutureConvention(const std::string& id, const BusinessDaysAfter& businessDaysAfter,
16491649
const std::string& contractFrequency, const std::string& calendar,
@@ -1730,7 +1730,7 @@ class CommodityFutureConvention : public Convention {
17301730
QuantLib::Month oneContractMonth_;
17311731
QuantLib::Integer offsetDays_;
17321732
QuantLib::BusinessDayConvention bdc_;
1733-
1733+
17341734

17351735
std::string strDayOfMonth_;
17361736
std::string strNth_;
@@ -1756,17 +1756,17 @@ class CommodityFutureConvention : public Convention {
17561756
QuantLib::Natural hoursPerDay_;
17571757
QuantLib::ext::optional<OffPeakPowerIndexData> offPeakPowerIndexData_;
17581758
std::string indexName_;
1759-
1759+
17601760
std::string strOptionContractFrequency_;
1761-
1761+
17621762
OptionAnchorType optionAnchorType_;
17631763
std::string strOptionExpiryOffset_;
17641764
std::string strOptionExpiryDay_;
17651765
std::string strOptionNth_;
17661766
std::string strOptionWeekday_;
17671767
std::string strOptionCalendarDaysBefore_;
17681768
std::string strOptionMinBusinessDaysBefore_;
1769-
1769+
17701770
QuantLib::Frequency optionContractFrequency_;
17711771
QuantLib::Natural optionExpiryOffset_;
17721772
QuantLib::Natural optionNth_;
@@ -1777,8 +1777,8 @@ class CommodityFutureConvention : public Convention {
17771777

17781778
std::set<QuantLib::Month> validContractMonths_;
17791779
std::string savingsTime_;
1780-
// If its averaging Future but the front month is spot averaged and
1781-
// balance of the month price is the average price of the remaining
1780+
// If its averaging Future but the front month is spot averaged and
1781+
// balance of the month price is the average price of the remaining
17821782
// future days in contract
17831783
bool balanceOfTheMonth_;
17841784
std::string balanceOfTheMonthPricingCalendarStr_;

OREData/ored/marketdata/yieldcurve.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,10 +2185,12 @@ void YieldCurve::addFutures(const std::size_t index, const QuantLib::ext::shared
21852185

21862186
// Create a MM future helper
21872187
QL_REQUIRE(
2188-
futureConvention->dateGenerationRule() == FutureConvention::DateGenerationRule::IMM,
2189-
"For MM Futures only 'IMM' is allowed as the date generation rule, check the future convention '"
2188+
futureConvention->dateGenerationRule() == FutureConvention::DateGenerationRule::IMM ||
2189+
futureConvention->dateGenerationRule() == FutureConvention::DateGenerationRule::SecondThursday,
2190+
"For MM Futures only 'IMM' or 'SecondThursday' are allowed as date generation rules, check the future convention '"
21902191
<< segment->conventionsID() << "'");
2191-
Date immDate = getMmFutureExpiryDate(futureQuote->expiryMonth(), futureQuote->expiryYear());
2192+
Date immDate = getMmFutureExpiryDate(futureQuote->expiryMonth(), futureQuote->expiryYear(),
2193+
futureConvention->dateGenerationRule());
21922194

21932195
if (immDate < asofDate_) {
21942196
DLOG("Skipping the " << io::ordinal(i + 1) << " money market future instrument because its "
@@ -2197,8 +2199,13 @@ void YieldCurve::addFutures(const std::size_t index, const QuantLib::ext::shared
21972199
continue;
21982200
}
21992201

2202+
// Determine the futures type for validation
2203+
Futures::Type futuresType = (futureConvention->dateGenerationRule() == FutureConvention::DateGenerationRule::IMM)
2204+
? Futures::IMM
2205+
: Futures::Custom;
2206+
22002207
auto helper = QuantLib::ext::make_shared<FuturesRateHelper>(futureQuote->quote(), immDate,
2201-
futureConvention->index());
2208+
futureConvention->index(), 0.0, futuresType);
22022209

22032210
instruments.push_back(
22042211
{helper, "Short MM Future", marketQuote->name(), marketQuote->quote()->value(),

OREData/ored/utilities/marketdata.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,17 @@ std::pair<Date, Date> getOiFutureStartEndDate(QuantLib::Month expiryMonth, Quant
332332
return std::make_pair(startDate, endDate);
333333
}
334334

335-
Date getMmFutureExpiryDate(QuantLib::Month expiryMonth, QuantLib::Natural expiryYear) {
335+
Date getMmFutureExpiryDate(QuantLib::Month expiryMonth, QuantLib::Natural expiryYear,
336+
FutureConvention::DateGenerationRule rule) {
336337
Date refDate(1, expiryMonth, expiryYear);
337-
Date immDate = IMM::nextDate(refDate, false);
338-
return immDate;
338+
339+
if (rule == FutureConvention::DateGenerationRule::IMM) {
340+
return IMM::nextDate(refDate, false); // Third Wednesday
341+
} else if (rule == FutureConvention::DateGenerationRule::SecondThursday) {
342+
return Date::nthWeekday(2, Thursday, refDate.month(), refDate.year());
343+
} else {
344+
QL_FAIL("getMmFutureExpiryDate: DateGenerationRule '" << rule << "' not supported for MM Futures");
345+
}
339346
}
340347

341348
std::string fxIndexNameForDailyLowsOrHighs(const QuantLib::ext::shared_ptr<QuantExt::FxIndex>& fxIndex, bool lows) {

OREData/ored/utilities/marketdata.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ std::pair<Date, Date> getOiFutureStartEndDate(QuantLib::Month expiryMonth, Quant
110110
QuantLib::Period tenor, FutureConvention::DateGenerationRule rule,
111111
const QuantLib::Calendar& calendar);
112112

113-
Date getMmFutureExpiryDate(QuantLib::Month expiryMonth, QuantLib::Natural expiryYear);
113+
Date getMmFutureExpiryDate(QuantLib::Month expiryMonth, QuantLib::Natural expiryYear,
114+
FutureConvention::DateGenerationRule rule = FutureConvention::DateGenerationRule::IMM);
114115

115116
/*! convert the creditCurveId into the internal name for the index tranche credit curve*/
116117
std::string indexTrancheSpecificCreditCurveName(const std::string& creditCurveId, const double assumedRecoveryRate);

OREData/ored/utilities/parsers.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1028,8 +1028,10 @@ FutureConvention::DateGenerationRule parseFutureDateGenerationRule(const std::st
10281028
return FutureConvention::DateGenerationRule::IMM;
10291029
else if (s == "FirstDayOfMonth")
10301030
return FutureConvention::DateGenerationRule::FirstDayOfMonth;
1031+
else if (s == "SecondThursday")
1032+
return FutureConvention::DateGenerationRule::SecondThursday;
10311033
else {
1032-
QL_FAIL("FutureConvention / DateGenerationRule '" << s << "' not known, expect 'IMM' or 'FirstDayOfMonth'");
1034+
QL_FAIL("FutureConvention / DateGenerationRule '" << s << "' not known, expect 'IMM', 'FirstDayOfMonth', or 'SecondThursday'");
10331035
}
10341036
}
10351037

@@ -1038,6 +1040,8 @@ std::ostream& operator<<(std::ostream& os, FutureConvention::DateGenerationRule
10381040
return os << "IMM";
10391041
else if (t == FutureConvention::DateGenerationRule::FirstDayOfMonth)
10401042
return os << "FirstDayOfMonth";
1043+
else if (t == FutureConvention::DateGenerationRule::SecondThursday)
1044+
return os << "SecondThursday";
10411045
else {
10421046
QL_FAIL("Internal error: unknown FutureConvention::DateGenerationRule - check implementation of operator<< "
10431047
"for this enum");

xsd/ore_types.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,7 @@
774774
<xs:restriction base="xs:string">
775775
<xs:enumeration value="IMM"/>
776776
<xs:enumeration value="FirstDayOfMonth"/>
777+
<xs:enumeration value="SecondThursday"/>
777778
</xs:restriction>
778779
</xs:simpleType>
779780

0 commit comments

Comments
 (0)